home *** CD-ROM | disk | FTP | other *** search
/ Clickx 115 / Clickx 115.iso / software / tools / windows / tails-i386-0.16.iso / live / filesystem.squashfs / usr / share / perl5 / Foomatic / DB.pm next >
Encoding:
Perl POD Document  |  2010-06-04  |  189.3 KB  |  6,361 lines

  1.  
  2. package Foomatic::DB;
  3. use Exporter;
  4. use Encode;
  5. @ISA = qw(Exporter);
  6.  
  7. @EXPORT_OK = qw(normalizename comment_filter
  8.         get_overview
  9.         getexecdocs
  10.         translate_printer_id
  11.         );
  12. @EXPORT = qw(ppdtoperl ppdfromvartoperl);
  13.  
  14. use Foomatic::Defaults qw(:DEFAULT $DEBUG);
  15. use Data::Dumper;
  16. use POSIX;                      # for rounding integers
  17. use strict;
  18.  
  19. my $ver = '$Revision$ ';
  20.  
  21. # constructor for Foomatic::DB
  22. sub new {
  23.     my $type = shift(@_);
  24.     my $this = bless {@_}, $type;
  25.     $this->{'language'} = "C";
  26.     return $this;
  27. }
  28.  
  29. # A map from the database's internal one-letter driver types to English
  30. my %driver_types = ('F' => 'Filter',
  31.             'P' => 'Postscript',
  32.             'U' => 'Ghostscript Uniprint',
  33.             'G' => 'Ghostscript');
  34.  
  35. # Translate old numerical PostGreSQL printer IDs to the new clear text ones.
  36. sub translate_printer_id {
  37.     my ($oldid) = @_;
  38.     # Read translation table for the printer IDs
  39.     my $translation_table = "$libdir/db/oldprinterids";
  40.     open TRTAB, "< $translation_table" or return $oldid;
  41.     while (<TRTAB>) {
  42.     chomp;
  43.     my $searcholdid = quotemeta($oldid);
  44.     if (/^\s*$searcholdid\s+(\S+)\s*$/) {
  45.         # ID found, return new ID
  46.         my $newid = $1;
  47.         close TRTAB;
  48.         return $newid;
  49.     }
  50.     }
  51.     # ID not found, return original one
  52.     close TRTAB;
  53.     return $oldid;
  54. }
  55.  
  56. # Set language for localized answers
  57. sub set_language {
  58.     my ($this, $language) = @_;
  59.     $this->{'language'} = $language;
  60. }
  61.  
  62. # List of driver names
  63. sub get_driverlist {
  64.     my ($this) = @_;
  65.     return $this->_get_xml_filelist('source/driver');
  66. }
  67.  
  68. # List of printer id's
  69. sub get_printerlist {
  70.     my ($this) = @_;
  71.     return $this->_get_xml_filelist('source/printer');
  72. }
  73.  
  74. sub get_overview {
  75.     my ($this, $rebuild, $cupsppds) = @_;
  76.  
  77.     # In-memory cache only for this process
  78.     return $this->{'overview'} if defined($this->{'overview'}) &&
  79.     !$rebuild;
  80.     $this->{'overview'} = undef;
  81.  
  82.     # Read on-disk cache file if we have one
  83.     if (defined($this->{'overviewfile'})) {
  84.         if (!$rebuild && (-r $this->{'overviewfile'})) {
  85.         if (open CFILE, "< $this->{'overviewfile'}") {
  86.         my $output = join('', <CFILE>);
  87.         close CFILE;
  88.         # Only output the cashed page if it was really
  89.         # completely written Before introduction of this
  90.         # measure pages would not display due to an incomplete
  91.         # cache file until the next page rebuild (or until
  92.         # manually nuking the cache).
  93.         if ($output =~ m!\]\;\s*$!s) {
  94.             my $VAR1;
  95.             if (eval $output) {
  96.             $this->{'overview'} = $VAR1;
  97.             return $this->{'overview'};
  98.             }
  99.         }
  100.         }
  101.     }
  102.     }
  103.  
  104.     # Build a new overview
  105.     my $otype = ($cupsppds ? '-C' : '-O');
  106.     $otype .= ' -n' if ($cupsppds == 1);
  107.     # Generate overview Perl data structure from database
  108.     my $VAR1;
  109.     eval `$bindir/foomatic-combo-xml $otype -l '$libdir' | $bindir/foomatic-perl-data -O -l $this->{'language'}` ||
  110.     die ("Could not run \"foomatic-combo-xml\"/\"foomatic-perl-data\"!");
  111.     $this->{'overview'} = $VAR1;
  112.  
  113.     # Write on-disk cache file if we have one
  114.     if (defined($this->{'overviewfile'})) {
  115.     if (open CFILE, "> $this->{'overviewfile'}") {
  116.         print CFILE Dumper($this->{'overview'});
  117.         close CFILE;
  118.     }
  119.     }
  120.  
  121.     return $this->{'overview'};
  122. }
  123.  
  124. sub get_overview_xml {
  125.     my ($this, $compile) = @_;
  126.  
  127.     open( FCX, "$bindir/foomatic-combo-xml -O -l '$libdir'|")
  128.     or die "Can't execute $bindir/foomatic-combo-xml -O -l '$libdir'";
  129.     $_ = join('', <FCX>);
  130.     close FCX;
  131.     return $_;
  132. }
  133.  
  134. sub get_combo_data_xml {
  135.     my ($this, $drv, $poid, $withoptions) = @_;
  136.  
  137.     # Insert the default option settings if there are some and the user
  138.     # desires it.
  139.     my $options = "";
  140.     if (($withoptions) && (defined($this->{'dat'}))) {
  141.     my $dat = $this->{'dat'};
  142.     for my $arg (@{$dat->{'args'}}) {
  143.         my $name = $arg->{'name'};
  144.         my $default = $arg->{'default'};
  145.         if (($name) && ($default)) {
  146.         $options .= " -o '$name'='$default'";
  147.         }
  148.     }
  149.     }
  150.  
  151.     open( FCX, "$bindir/foomatic-combo-xml -d '$drv' -p '$poid'$options -l '$libdir'|")
  152.     or die "Can't execute $bindir/foomatic-combo-xml -d '$drv' -p '$poid'$options -l '$libdir'";
  153.     $_ = join('', <FCX>);
  154.     close FCX;
  155.     return $_;
  156. }
  157.  
  158. sub get_printer {
  159.     my ($this, $poid) = @_;
  160.     # Generate printer Perl data structure from database
  161.     my $VAR1;
  162.     if (-r "$libdir/db/source/printer/$poid.xml") {
  163.     eval (`$bindir/foomatic-perl-data -P -l $this->{'language'} '$libdir/db/source/printer/$poid.xml'`) ||
  164.         die ("Could not run \"foomatic-perl-data\"!");
  165.     } else {
  166.     my ($make, $model);
  167.     if ($poid =~ /^([^\-]+)\-(.*)$/) {
  168.         $make = $1;
  169.         $model = $2;
  170.         $make =~ s/_/ /g;
  171.         $model =~ s/_/ /g;
  172.     } else {
  173.         $make = $poid;
  174.         $make =~ s/_/ /g;
  175.         $model = "Unknown model";
  176.     }
  177.     $VAR1 = {
  178.         'id' => $poid,
  179.         'make' => $make,
  180.         'model' => $model,
  181.         'noxmlentry' => 1
  182.     }
  183.     }
  184.     return $VAR1;
  185. }
  186.  
  187. sub printer_exists {
  188.     my ($this, $poid) = @_;
  189.     # Check whether a printer XML file exists in the database
  190.     return 1 if (-r "$libdir/db/source/printer/$poid.xml");
  191.     return undef;
  192. }
  193.  
  194. sub get_printer_xml {
  195.     my ($this, $poid) = @_;
  196.     return $this->_get_object_xml("source/printer/$poid", 1);
  197. }
  198.  
  199. sub get_driver {
  200.     my ($this, $drv) = @_;
  201.     # Generate driver Perl data structure from database
  202.     my $VAR1;
  203.     if (-r "$libdir/db/source/driver/$drv.xml") {
  204.     eval (`$bindir/foomatic-perl-data -D -l $this->{'language'} '$libdir/db/source/driver/$drv.xml'`) ||
  205.         die ("Could not run \"foomatic-perl-data\"!");
  206.     } else {
  207.     return undef;
  208.     }
  209.     return $VAR1;
  210. }
  211.  
  212. sub get_driver_xml {
  213.     my ($this, $drv) = @_;
  214.     return $this->_get_object_xml("source/driver/$drv", 1);
  215. }
  216.  
  217. # Utility query function sorts of things:
  218.  
  219. sub get_printers_for_driver {
  220.     my ($this, $drv) = @_;
  221.  
  222.     my @printerlist = ();
  223.  
  224.     #my $driver = $this->get_driver($drv);
  225.     #if (defined($driver)) {
  226.     #@printerlist = map { $_->{'id'} } @{$driver->{'printers'}};
  227.     #}
  228.  
  229.     $this->get_overview();
  230.     for my $p (@{$this->{'overview'}}) {
  231.     if (member($drv, @{$p->{'drivers'}})) {
  232.         push(@printerlist, $p->{'id'});
  233.     }
  234.     }
  235.  
  236.     return @printerlist;
  237. }
  238.  
  239. # Routine lookup; just examine the overview
  240. sub get_drivers_for_printer {
  241.     my ($this, $printer) = @_;
  242.  
  243.     my @drivers = ();
  244.  
  245.     my $over = $this->get_overview();
  246.  
  247.     my $p;
  248.     for $p (@{$over}) {
  249.     if ($p->{'id'} eq $printer) {
  250.         return @{$p->{'drivers'}};
  251.     }
  252.     }
  253.  
  254.     return undef;
  255. }
  256.  
  257.  
  258. # Clean some manufacturer's names (for printer search function, taken
  259. # from printerdrake, former printer setup tool of Mandriva Linux)
  260. sub clean_manufacturer_name {
  261.     my ($make) = @_;
  262.     #$make =~ s/^Canon\W.*$/Canon/i;
  263.     #$make =~ s/^Lexmark.*$/Lexmark/i;
  264.     $make =~ s/^Hewlett?[_\s\-]*Packard/HP/i;
  265.     $make =~ s/^Seiko[_\s\-]*Epson/Epson/i;
  266.     $make =~ s/^Kyocera[_\s\-]*Mita/Kyocera/i;
  267.     $make =~ s/^CItoh/C.Itoh/i;
  268.     $make =~ s/^Oki(|[_\s\-]*Data)$/Oki/i;
  269.     $make =~ s/^(SilentWriter2?|ColorMate)/NEC/i;
  270.     $make =~ s/^(XPrint|Majestix)/Xerox/i;
  271.     $make =~ s/^QMS-PS/QMS/i;
  272.     $make =~ s/^konica([_\s\-]|)minolta/KONICA MINOLTA/i;
  273.     $make =~ s/^(Personal|LaserWriter)/Apple/i;
  274.     $make =~ s/^Digital/DEC/i;
  275.     $make =~ s/\s+Inc\.//i;
  276.     $make =~ s/\s+Corp\.//i;
  277.     $make =~ s/\s+SA\.//i;
  278.     $make =~ s/\s+S\.\s*A\.//i;
  279.     $make =~ s/\s+Ltd\.//i;
  280.     $make =~ s/\s+International//i;
  281.     $make =~ s/\s+Int\.//i;
  282.     return $make;
  283. }    
  284.  
  285.  
  286. # Clean some model names (taken from system-config-printer, printer setup
  287. # tool of Fedora/Red Hat, Ubuntu, and Mandriva
  288. sub clean_model_name {
  289.     my ($model) = @_;
  290.     $model =~ s/^Mita[_\s\-]+//i;
  291.     $model =~ s/^AL-(([CM][A-Z]?|)\d+)/AcuLaser $1PS/;
  292.     $model =~ s/\s*\(recommended\)//i;
  293.     $model =~ s/\s*-\s*PostScript\b//i;
  294.     $model =~ s/\s*-\s*BR-Script[123]?\b//i;
  295.     $model =~ s/\s*\bseries\b//i;
  296.     $model =~ s/\s*\bPS[123]?\b//i;
  297.     $model =~ s/\s*PS[123]?$//;
  298.     $model =~ s/\s*\bPXL//i;
  299.     $model =~ s/[\s_-]+BT\b//i;
  300.     $model =~ s/\s*\(Bluetooth\)//i;
  301.     $model =~ s/\s*-\s*(RC|Ver(|sion))\s*-*\s*[0-9\.]+//i;
  302.     $model =~ s/\s*-\s*(RC|Ver(|sion))\b//i;
  303.     $model =~ s/\s*PostScript\s*$//i;
  304.     $model =~ s/\s*BR-Script[123]?\s*$//i;
  305.     $model =~ s/\s*\(\s*\)//i;
  306.     $model =~ s/\s*[\-\/]\s*$//i;
  307.     return $model;
  308. }
  309.  
  310.  
  311. # Guess manufacturer by description with only model name (for printer
  312. # search function, taken from printerdrake, printer setup tool of
  313. # Mandriva Linux)
  314.  
  315. sub guessmake {
  316.  
  317.     my ($description) = @_;
  318.  
  319.     my $manufacturer;
  320.     my $model;
  321.  
  322.     if ($description =~
  323.     /^\s*(DeskJet|LaserJet|OfficeJet|PSC|PhotoSmart)\b/i) {
  324.     # HP printer
  325.     $manufacturer = "HP";
  326.     $model = $description;
  327.     } elsif ($description =~
  328.          /^\s*(Stylus|EPL|AcuLaser)\b/i) {
  329.     # Epson printer
  330.     $manufacturer = "Epson";
  331.     $model = $description;
  332.     } elsif ($description =~
  333.          /^\s*(Aficio)\b/i) {
  334.     # Ricoh printer
  335.     $manufacturer = "Ricoh";
  336.     $model = $description;
  337.     } elsif ($description =~
  338.          /^\s*(Optra|Color\s+JetPrinter)\b/i) {
  339.     # Lexmark printer
  340.     $manufacturer = "Lexmark";
  341.     $model = $description;
  342.     } elsif ($description =~
  343.          /^\s*(imageRunner|Pixma|Pixus|BJC|LBP)\b/i) {
  344.     # Canon printer
  345.     $manufacturer = "Canon";
  346.     $model = $description;
  347.     } elsif ($description =~
  348.          /^\s*(Phaser|DocuPrint|(Work|Document)\s*(Home|)Centre)\b/i) {
  349.     # Xerox printer
  350.     $manufacturer = "Xerox";
  351.     $model = $description;
  352.     } elsif (($description =~ /^\s*(KONICA\s*MINOLTA)\s+(\S.*)$/i) ||
  353.          ($description =~ /^\s*(\S*)\s+(\S.*)$/)) {
  354.     $manufacturer = $1 if $manufacturer eq "";
  355.     $model = $2 if $model eq "";
  356.     }
  357.     return ($manufacturer, $model);
  358. }
  359.  
  360. # Normalize a string, so that for a search only letters
  361. # (case-insensitive), numbers and boundaries between letter blocks and
  362. # number blocks are considered. The pipe '|' as separator between make
  363. # and model is also considered. Blocks of other characters are
  364. # replaced by a single space and boundaries between letters and
  365. # numbers are marked with a single space.
  366. sub normalize {
  367.     my ($str) = @_;
  368.     $str = lc($str);
  369.     $str =~ s/\+/plus/g;
  370.     $str =~ s/[^a-z0-9\|]+/ /g;
  371.     $str =~ s/(?<=[a-z])(?=[0-9])/ /g;
  372.     $str =~ s/(?<=[0-9])(?=[a-z])/ /g;
  373.     $str =~ s/ //g;
  374.     return $str;
  375. }
  376.  
  377. # Find a printer in the database based on an auto-detected device ID
  378. # or a user-typed search term
  379. sub find_printer {
  380.     my ($this, $searchterm, $mode, $output) = @_;
  381.     # $mode = 0: Everything (default)
  382.     # $mode = 1: No matches on only the manufacturer
  383.     # $mode = 2: No matches on only the manufacturer or only the model
  384.     # $mode = 3: Exact matches of device ID, make/model, or Foomatic ID
  385.     #            plus matches of the page description language
  386.     # $mode = 4: Exact matches of device ID, make/model, or Foomatic ID
  387.     #            only
  388.     # $output = 0: Everything
  389.     # $output = 1: Only best match class (default)
  390.     # $output = 2: Only best match
  391.  
  392.     # Correct options
  393.     $mode = 0 if !defined $mode;
  394.     $mode = 0 if $mode < 0;
  395.     $mode = 4 if $mode > 4;
  396.     $output = 1 if !defined $output;
  397.     $output = 0 if $output < 0;
  398.     $output = 2 if $output > 2;
  399.  
  400.     my $over = $this->get_overview();
  401.  
  402.     my %results;
  403.  
  404.     # Parse the search term
  405.     my ($automake, $automodel, $autodescr, $autocmdset, $autosku);
  406.     my $deviceid = 0;
  407.  
  408.     # Do we have a device ID?
  409.     if ($searchterm =~ /(MFG|MANUFACTURER):\s*([^:;]+);?/i) {
  410.     $automake = $2;
  411.     $deviceid = 1;
  412.     }
  413.     if ($searchterm =~ /(MDL|MODEL):\s*([^:;]+);?/i) {
  414.     $automodel = $2;
  415.     $automodel =~ s/\s+$//;
  416.     $deviceid = 1;
  417.     }
  418.     if ($searchterm =~ /(DES|DESCRIPTION):\s*([^:;]+);?/i) {
  419.     $autodescr = $2;
  420.     $autodescr =~ s/\s+$//;
  421.     $deviceid = 1;
  422.     }
  423.     if ($searchterm =~ /(CMD|COMMANDS?\s?SET):\s*([^:;]+);?/i) {
  424.     $autocmdset = $2;
  425.     $deviceid = 1;
  426.     }
  427.     if ($searchterm =~ /(SKU):\s*([^:;]+);?/i) {
  428.     $autosku = $2;
  429.     $autosku =~ s/\s+$//;
  430.     $deviceid = 1;
  431.     }
  432.  
  433.     # Search term is not a device ID
  434.     if (!$deviceid) {
  435.     if ($searchterm =~ /^([^\|]+)\|([^\|]+|)(\|.*?|)$/) {
  436.         $automake = $1;
  437.         $automodel = $2;
  438.     } else {
  439.         $autodescr = $searchterm;
  440.     }
  441.     }
  442.  
  443.     # This is the algorithm used in printerdrake (printer setup tool
  444.     # of Mandriva Linux) to match results of the printer auto-detection
  445.     # with the printer database
  446.  
  447.     # Clean some manufacturer's names
  448.     my $descrmake = clean_manufacturer_name($automake);
  449.  
  450.     # Generate data to match human-readable make/model names
  451.     # of Foomatic database
  452.     my $descr;
  453.     if ($automake && $autosku) {
  454.     $descr = "$descrmake|$autosku";
  455.     } elsif ($automake && $automodel) {
  456.     $descr = "$descrmake|$automodel";
  457.     } elsif ($autodescr && (length($autodescr) > 5)) {
  458.     my ($mf, $md) =
  459.         guessmake($autodescr);
  460.     $descrmake = clean_manufacturer_name($mf);
  461.     $descr = "$descrmake|$md";
  462.     } elsif ($automodel) {
  463.     my ($mf, $md) =
  464.         guessmake($automodel);
  465.     $descrmake = clean_manufacturer_name($mf);
  466.     $descr = "$descrmake|$md";
  467.     } elsif ($automake) {
  468.     $descr = "$descrmake|";
  469.     }
  470.  
  471.     # Remove manufacturer's name from the beginning of the
  472.     # description (do not do this with manufacturer names which
  473.     # contain odd characters)
  474.     $descr =~ s/^$descrmake\|\s*$descrmake\s*/$descrmake|/i
  475.     if $descrmake && 
  476.     $descrmake !~ m![\\/\(\)\[\]\|\.\$\@\%\*\?]!;
  477.  
  478.     # Clean up the description from noise which makes the best match
  479.     # difficult
  480.     $descr =~ s/\s+[Ss]eries//i;
  481.     $descr =~ s/\s+\(?[Pp]rinter\)?$//i;
  482.  
  483.     # Try to find an exact match, check both whether the detected
  484.     # make|model is in the make|model of the database entry and vice versa
  485.     # If there is more than one matching database entry, the longest match
  486.     # counts.
  487.     my $matchlength = -1000;
  488.     my $bestmatchlength = -1000;
  489.     my $p;
  490.   DBENTRY: for $p (@{$over}) {
  491.     # Try to match the device ID string of the auto-detection
  492.     if ($p->{make} =~ /Generic/i) {
  493.         # Database entry for generic printer, check printer
  494.         # languages (command set)
  495.         if ($p->{model} =~ m!PCL\s*5/5e!i) {
  496.         # Generic PCL 5/5e Printer
  497.         if ($autocmdset =~
  498.             /(^|[:,])PCL\s*\-*\s*(5|)($|[,;])/i) {
  499.             $matchlength = 70;
  500.             $bestmatchlength = $matchlength if
  501.             $bestmatchlength < $matchlength;
  502.             $results{$p->{id}} = $matchlength if
  503.             (!defined($results{$p->{id}}) ||
  504.              ($results{$p->{id}} < $matchlength));
  505.             next;
  506.         }
  507.         } elsif ($p->{model} =~ m!PCL\s*(6|XL)!i) {
  508.         # Generic PCL 6/XL Printer
  509.         if ($autocmdset =~
  510.             /(^|[:,])PCL\s*\-*\s*(6|XL)($|[,;])/i) {
  511.             $matchlength = 80;
  512.             $bestmatchlength = $matchlength if
  513.             $bestmatchlength < $matchlength;
  514.             $results{$p->{id}} = $matchlength if
  515.             (!defined($results{$p->{id}}) ||
  516.              ($results{$p->{id}} < $matchlength));
  517.             next;
  518.         }
  519.         } elsif ($p->{model} =~ m!(PostScript)!i) {
  520.         # Generic PostScript Printer
  521.         if ($autocmdset =~
  522.             /(^|[:,\s])(PS|POSTSCRIPT)[^:;,]*($|[,;])/i) {
  523.             $matchlength = 90;
  524.             $bestmatchlength = $matchlength if
  525.             $bestmatchlength < $matchlength;
  526.             $results{$p->{id}} = $matchlength if
  527.             (!defined($results{$p->{id}}) ||
  528.              ($results{$p->{id}} < $matchlength));
  529.             next;
  530.         }
  531.         }
  532.  
  533.     } else {
  534.         # "Real" manufacturer, check manufacturer, model, and/or
  535.         # description
  536.         my $matched = 1;
  537.         my ($mfg, $mdl, $des, $sku);
  538.         my $ieee1284 = deviceIDfromDBEntry($p);
  539.         if ($ieee1284 =~ /(MFG|MANUFACTURER):\s*([^:;]+);?/i) {
  540.         $mfg = $2;
  541.         }
  542.         if ($ieee1284 =~ /(MDL|MODEL):\s*([^:;]+);?/i) {
  543.         $mdl = $2;
  544.         $mdl =~ s/\s+$//;
  545.         }
  546.         if ($ieee1284 =~ /(DES|DESCRIPTION):\s*([^:;]+);?/i) {
  547.         $des = $2;
  548.         $des =~ s/\s+$//;
  549.         }
  550.         if ($ieee1284 =~ /(SKU):\s*([^:;]+);?/i) {
  551.         $sku = $2;
  552.         $sku =~ s/\s+$//;
  553.         }
  554.         if ($mfg) {
  555.         if ($mfg ne $automake) {
  556.             $matched = 0;
  557.         }
  558.         }
  559.         if ($mdl) {
  560.         if ($mdl ne $automodel) {
  561.             $matched = 0;
  562.         }
  563.         }
  564.         if ($des) {
  565.         if ($des ne $autodescr) {
  566.             $matched = 0;
  567.         }
  568.         }
  569.         if ($sku && $autosku) {
  570.         if ($sku ne $autosku) {
  571.             $matched = 0;
  572.         }
  573.         }
  574.         if ($matched &&
  575.         ($des || ($mfg && ($mdl || ($sku && $autosku))))) {
  576.         # Full match to known auto-detection data
  577.         $matchlength = 1000;
  578.         $bestmatchlength = $matchlength if
  579.             $bestmatchlength < $matchlength;
  580.         $results{$p->{id}} = $matchlength if
  581.                 (!defined($results{$p->{id}}) ||
  582.                  ($results{$p->{id}} < $matchlength)); 
  583.         next;
  584.         }
  585.     }
  586.  
  587.     # Try to match the (human-readable) make and model of the
  588.     # Foomatic database or of the PPD file
  589.     my $dbmakemodel = "$p->{make}|$p->{model}";
  590.  
  591.     # At first try to match make and model, then only model and
  592.     # after that only make
  593.     my $searchtasks = [[$descr, $dbmakemodel, 0],
  594.                [$searchterm, $p->{model}, -200],
  595.                [clean_manufacturer_name($searchterm),
  596.                 $p->{make}, -300],
  597.                [$searchterm, $p->{id}, 0]];
  598.  
  599.     foreach my $task (@{$searchtasks}) {
  600.  
  601.         # Do not try to match search terms or database entries without
  602.         # real content
  603.         next unless $task->[0] =~ /[a-z]/i;
  604.         next unless $task->[1] =~ /[a-z]/i;
  605.  
  606.         # If make and model match exactly, we have found the correct
  607.         # entry and we can stop searching human-readable makes and
  608.         # models
  609.         if (normalize($task->[1]) eq normalize($task->[0])) {
  610.         $matchlength = 100;
  611.         $bestmatchlength = $matchlength + $task->[2] if
  612.             $bestmatchlength < $matchlength + $task->[2];
  613.         $results{$p->{id}} = $matchlength + $task->[2] if
  614.                 (!defined($results{$p->{id}}) ||
  615.                  ($results{$p->{id}} < $matchlength)); 
  616.         next DBENTRY;
  617.         }
  618.  
  619.         # Matching a part of the human-readable makes and models
  620.         # should only be done if the search term is not the name of
  621.         # an old model, otherwise the newest, not yet listed models
  622.         # match with the oldest model of the manufacturer (as the
  623.         # Epson Stylus Photo 900 with the original Epson Stylus Photo)
  624.         my @badsearchterms = 
  625.         ("HP|DeskJet",
  626.          "HP|LaserJet",
  627.          "HP|DesignJet",
  628.          "HP|OfficeJet",
  629.          "HP|PhotoSmart",
  630.          "EPSON|Stylus",
  631.          "EPSON|Stylus Color",
  632.          "EPSON|Stylus Photo",
  633.          "EPSON|Stylus Pro",
  634.          "XEROX|WorkCentre",
  635.          "XEROX|DocuPrint");
  636.         if (!member($task->[0], @badsearchterms)) {
  637.         my $searcht = normalize($task->[0]);
  638.         my $lsearcht = length($searcht);
  639.         $searcht =~ s!([\\/\(\)\[\]\|\.\$\@\%\*\?])!\\$1!g;
  640.         $searcht =~ s!(\\\|)!$1.*!g;
  641.         my $s = normalize($task->[1]);
  642.         if ((1 || $lsearcht >= $matchlength) &&
  643.             $s =~ m!$searcht!i) {
  644.             $matchlength = $lsearcht;
  645.             $bestmatchlength = $matchlength + $task->[2] if
  646.             $bestmatchlength < $matchlength + $task->[2];
  647.             $results{$p->{id}} = $matchlength + $task->[2] if
  648.                 (!defined($results{$p->{id}}) ||
  649.                  ($results{$p->{id}} < $matchlength)); 
  650.         }
  651.         }
  652.         if (!member($task->[1], @badsearchterms)) {
  653.         my $searcht = normalize($task->[1]);
  654.         my $lsearcht = length($searcht);
  655.         $searcht =~ s!([\\/\(\)\[\]\|\.\$\@\%\*\?])!\\$1!g;
  656.         $searcht =~ s!(\\\|)!$1.*!g;
  657.         my $s = normalize($task->[0]);
  658.         if ((1 || $lsearcht >= $matchlength) &&
  659.             $s =~ m!$searcht!i) {
  660.             $matchlength = $lsearcht;
  661.             $bestmatchlength = $matchlength + $task->[2] if
  662.             $bestmatchlength < $matchlength + $task->[2];
  663.             $results{$p->{id}} = $matchlength + $task->[2] if
  664.                 (!defined($results{$p->{id}}) ||
  665.                  ($results{$p->{id}} < $matchlength)); 
  666.         }
  667.         }
  668.     }
  669.     }
  670.  
  671.     return grep {
  672.     ((($mode == 4) && ($results{$_} >= 100)) ||
  673.      (($mode == 3) && ($results{$_} > 60)) ||
  674.      (($mode == 2) && ($results{$_} > -100)) ||
  675.      (($mode == 1) && ($results{$_} > -200)) ||
  676.      ($mode == 0)) &&
  677.     (($output == 0) ||
  678.      (($output == 1) &&
  679.       !((($bestmatchlength >= 100) && ($results{$_} < 100)) || 
  680.         (($bestmatchlength >= 60) && ($results{$_} < 60)) || 
  681.         (($bestmatchlength >= 0) && ($results{$_} < 0)) || 
  682.         (($bestmatchlength >= -100) && ($results{$_} < -100)) || 
  683.         (($bestmatchlength >= -200) && ($results{$_} < -200)) || 
  684.         (($bestmatchlength >= -300) && ($results{$_} < -300)) || 
  685.         (($bestmatchlength >= -400) && ($results{$_} < -400)))) ||
  686.      (($output == 2) &&
  687.       ($results{$_} == $bestmatchlength)))
  688.     } sort { $results{$b} <=> $results{$a} } keys(%results);
  689. }
  690.  
  691. # This function sorts the options at first by their group membership and
  692. # then by their names appearing in the list of functional areas. This way
  693. # it will be made easier to build the PPD file with option groups and in
  694. # user interfaces options will appear sorted by their functionality.
  695. sub sortargs {
  696.  
  697.     # All sorting done case-insensitive and characters which are not a
  698.     # letter or number are taken out!!
  699.  
  700.     # List of typical option names to appear at first
  701.     # The terms must fit to the beginning of the line, terms which must fit
  702.     # exactly must have '\$' in the end.
  703.     my @standardopts = (
  704.             # The most important composite option
  705.             "printoutmode",
  706.             # Options which appear in the "General" group in 
  707.             # CUPS and similar media handling options
  708.             "pagesize",
  709.             "papersize",
  710.             "mediasize",
  711.             "inputslot",
  712.             "papersource",
  713.             "mediasource",
  714.             "sheetfeeder",
  715.             "mediafeed",
  716.             "paperfeed",
  717.             "manualfeed",
  718.             "manual",
  719.             "outputtray",
  720.             "outputslot",
  721.             "outtray",
  722.             "faceup",
  723.             "facedown",
  724.             "mediatype",
  725.             "papertype",
  726.             "mediaweight",
  727.             "paperweight",
  728.             "duplex",
  729.             "sides",
  730.             "binding",
  731.             "tumble",
  732.             "notumble",
  733.             "media",
  734.             "paper",
  735.             # Other hardware options
  736.             "inktype",
  737.             "ink",
  738.             # Page choice/ordering options
  739.             "pageset",
  740.             "pagerange",
  741.             "pages",
  742.             "nup",
  743.             "numberup",
  744.             # Printout quality, colour/bw
  745.             "resolution",
  746.             "gsresolution",
  747.             "hwresolution",
  748.             "jclresolution",
  749.             "fastres",
  750.             "jclfastres",
  751.             "quality",
  752.             "printquality",
  753.             "printingquality",
  754.             "printoutquality",
  755.             "bitsperpixel",
  756.             "econo",
  757.             "jclecono",
  758.             "tonersav",
  759.             "photomode",
  760.             "photo",
  761.             "colormode",
  762.             "colourmode",
  763.             "color",
  764.             "colour",
  765.             "grayscale",
  766.             "gray",
  767.             "monochrome",
  768.             "mono",
  769.             "blackonly",
  770.             "colormodel",
  771.             "colourmodel",
  772.             "processcolormodel",
  773.             "processcolourmodel",
  774.             "printcolors",
  775.             "printcolours",
  776.             "outputtype",
  777.             "outputmode",
  778.             "printingmode",
  779.             "printoutmode",
  780.             "printmode",
  781.             "mode",
  782.             "imagetype",
  783.             "imagemode",
  784.             "image",
  785.             "dithering",
  786.             "dither",
  787.             "halftoning",
  788.             "halftone",
  789.             "floydsteinberg",
  790.             "ret\$",
  791.             "cret\$",
  792.             "photoret\$",
  793.             "smooth",
  794.             # Adjustments
  795.             "gammacorrection",
  796.             "gammacorr",
  797.             "gammageneral",
  798.             "mastergamma",
  799.             "stpgamma",
  800.             "gammablack",
  801.             "blackgamma",
  802.             "gammacyan",
  803.             "cyangamma",
  804.             "gammamagenta",
  805.             "magentagamma",
  806.             "gammayellow",
  807.             "yellowgamma",
  808.             "gammared",
  809.             "redgamma",
  810.             "gammagreen",
  811.             "greengamma",
  812.             "gammablue",
  813.             "bluegamma",
  814.             "gamma",
  815.             "density",
  816.             "stpdensity",
  817.             "hpljdensity",
  818.             "tonerdensity",
  819.             "inkdensity",
  820.             "brightness",
  821.             "stpbrightness",
  822.             "saturation",
  823.             "stpsaturation",
  824.             "hue",
  825.             "stphue",
  826.             "tint",
  827.             "stptint",
  828.             "contrast",
  829.             "stpcontrast",
  830.             "black",
  831.             "stpblack",
  832.             "cyan",
  833.             "stpcyan",
  834.             "magenta",
  835.             "stpmagenta",
  836.             "yellow",
  837.             "stpyellow",
  838.             "red",
  839.             "stpred",
  840.             "green",
  841.             "stpgreen",
  842.             "blue",
  843.             "stpblue"
  844.             );
  845.  
  846.     my @standardgroups = (
  847.               "general",
  848.               "media",
  849.               "quality",
  850.               "imag",
  851.               "color",
  852.               "output",
  853.               "finish",
  854.               "stapl",
  855.               "extra",
  856.               "install"
  857.               );
  858.  
  859.     my $compare;
  860.  
  861.     # Argument records
  862.     my $firstarg = $a;
  863.     my $secondarg = $b;
  864.  
  865.     # Bring the two option names into a standard form to compare them
  866.     # in a better way
  867.     my $first = normalizename(lc($firstarg->{'name'}));
  868.     $first =~ s/[\W_]//g;
  869.     my $second = normalizename(lc($secondarg->{'name'}));
  870.     $second =~ s/[\W_]//g;
  871.  
  872.     # group names
  873.     my $firstgr = $firstarg->{'group'};
  874.     my @firstgroup;
  875.     @firstgroup = split("/", $firstgr) if defined($firstgr); 
  876.     my $secondgr = $secondarg->{'group'};
  877.     my @secondgroup;
  878.     @secondgroup = split("/", $secondgr) if defined($secondgr);
  879.  
  880.     my $i = 0;
  881.  
  882.     # Compare groups
  883.     while ($firstgroup[$i] && $secondgroup[$i]) {
  884.  
  885.     # Normalize group names
  886.     my $firstgr = normalizename(lc($firstgroup[$i]));
  887.     $firstgr =~ s/[\W_]//g;
  888.     my $secondgr = normalizename(lc($secondgroup[$i]));
  889.     $secondgr =~ s/[\W_]//g;
  890.         
  891.     # Are the groups in the list of standard group names?
  892.     my $j;
  893.     for ($j = 0; $j <= $#standardgroups; $j++) {
  894.         my $firstinlist = ($firstgr =~ /^$standardgroups[$j]/);
  895.         my $secondinlist = ($secondgr =~ /^$standardgroups[$j]/);
  896.         if (($firstinlist) && (!$secondinlist)) {return -1};
  897.         if (($secondinlist) && (!$firstinlist)) {return 1};
  898.         if (($firstinlist) && ($secondinlist)) {last};
  899.     }
  900.  
  901.     # Compare normalized group names
  902.     $compare = $firstgr cmp $secondgr;
  903.     if ($compare != 0) {return $compare};
  904.  
  905.     # Compare original group names
  906.     $compare = $firstgroup[$i] cmp $secondgroup[$i];
  907.     if ($compare != 0) {return $compare};
  908.     
  909.     $i++;
  910.     }
  911.  
  912.     # The one with a deeper level in the group tree will come later
  913.     if ($firstgroup[$i]) {return 1};
  914.     if ($secondgroup[$i]) {return -1};
  915.  
  916.     # Sort by order parameter if the order parameters are different
  917.     if (defined($firstarg->{'order'}) && defined($secondarg->{'order'}) &&
  918.     $firstarg->{'order'} != $secondarg->{'order'}) {
  919.     return $firstarg->{'order'} cmp $secondarg->{'order'};
  920.     }
  921.  
  922.     # Check whether the argument names are in the @standardopts list
  923.     for ($i = 0; $i <= $#standardopts; $i++) {
  924.     my $firstinlist = ($first =~ /^$standardopts[$i]/);
  925.     my $secondinlist = ($second =~ /^$standardopts[$i]/);
  926.     if (($firstinlist) && (!$secondinlist)) {return -1};
  927.     if (($secondinlist) && (!$firstinlist)) {return 1};
  928.     if (($firstinlist) && ($secondinlist)) {last};
  929.     }
  930.  
  931.     # None of the search terms in the list, compare the standard-formed
  932.     # strings
  933.     $compare = ( $first cmp $second );
  934.     if ($compare != 0) {return $compare};
  935.  
  936.     # No other criteria fullfilled, compare the original input strings
  937.     return $firstarg->{'name'} cmp $secondarg->{'name'};
  938. }
  939.  
  940. sub sortvals {
  941.  
  942.     # All sorting done case-insensitive and characters which are not a letter
  943.     # or number are taken out!!
  944.  
  945.     # List of typical choice names to appear at first
  946.     # The terms must fit to the beginning of the line, terms which must fit
  947.     # exactly must have '\$' in the end.
  948.     my @standardvals = (
  949.             # Default setting
  950.             "default",
  951.             "printerdefault",
  952.             # "Neutral" setting
  953.             "None\$",
  954.             # Paper sizes
  955.             "letter\$",
  956.             #"legal",
  957.             "a4\$",
  958.             # Paper types
  959.             "plain",
  960.             # Printout Modes
  961.             "draft\$",
  962.             "draft\.gray",
  963.             "draft\.mono",
  964.             "draft\.",
  965.             "draft",
  966.             "normal\$",
  967.             "normal\.gray",
  968.             "normal\.mono",
  969.             "normal\.",
  970.             "normal",
  971.             "high\$",
  972.             "high\.gray",
  973.             "high\.mono",
  974.             "high\.",
  975.             "high",
  976.             "veryhigh\$",
  977.             "veryhigh\.gray",
  978.             "veryhigh\.mono",
  979.             "veryhigh\.",
  980.             "veryhigh",
  981.             "photo\$",
  982.             "photo\.gray",
  983.             "photo\.mono",
  984.             "photo\.",
  985.             "photo",
  986.             # Trays
  987.             "upper",
  988.             "top",
  989.             "middle",
  990.             "mid",
  991.             "lower",
  992.             "bottom",
  993.             "highcapacity",
  994.             "multipurpose",
  995.             "tray",
  996.             );
  997.  
  998.     # Do not waste time if the input strings are equal
  999.     if ($a eq $b) {return 0;}
  1000.  
  1001.     # Are the two strings numbers? Compare them numerically
  1002.     if (($a =~ /^[\d\.]+$/) && ($b =~ /^[\d\.]+$/)) {
  1003.     my $compare = ( $a <=> $b );
  1004.     if ($compare != 0) {return $compare};
  1005.     }
  1006.  
  1007.     # Bring the two option names into a standard form to compare them
  1008.     # in a better way
  1009.     my $first = lc($a);
  1010.     $first =~ s/[\W_]//g;
  1011.     my $second = lc($b);
  1012.     $second =~ s/[\W_]//g;
  1013.  
  1014.     # Check whether they are in the @standardvals list
  1015.     for (my $i = 0; $i <= $#standardvals; $i++) {
  1016.     my $firstinlist = ($first =~ /^$standardvals[$i]/);
  1017.     my $secondinlist = ($second =~ /^$standardvals[$i]/);
  1018.     if (($firstinlist) && (!$secondinlist)) {return -1};
  1019.     if (($secondinlist) && (!$firstinlist)) {return 1};
  1020.     if (($firstinlist) && ($secondinlist)) {last};
  1021.     }
  1022.     
  1023.     # None of the search terms in the list, compare the standard-formed 
  1024.     # strings
  1025.     my $compare = ( normalizename($first) cmp normalizename($second) );
  1026.     if ($compare != 0) {return $compare};
  1027.  
  1028.     # No other criteria fullfilled, compare the original input strings
  1029.     return $a cmp $b;
  1030. }
  1031.  
  1032. # Take driver/pid arguments and generate a Perl data structure for the
  1033. # Perl filter scripts. Sort the options and enumerated choices so that
  1034. # they get presented more nicely on frontends which do not sort by
  1035. # themselves
  1036.  
  1037. sub getdat ($ $ $) {
  1038.     my ($this, $drv, $poid) = @_;
  1039.  
  1040.     my $ppdfile;
  1041.  
  1042.     # Do we have a link to a custom PPD file for this driver in the
  1043.     # printer XML file? Then return the custom PPD
  1044.  
  1045.     my $p = $this->get_printer($poid);
  1046.     if (defined($p->{'drivers'})) {
  1047.     for my $d (@{$p->{'drivers'}}) {
  1048.         next if ($d->{'id'} ne $drv);
  1049.         $ppdfile = $d->{'ppd'} if defined($d->{'ppd'});
  1050.         last;
  1051.     }
  1052.     }
  1053.  
  1054.     # Do we have a PostScript printer and a link to a manufacturer-
  1055.     # supplied PPD file? Then return the manufacturer-supplied PPD
  1056.  
  1057.     if ($drv =~ /^Postscript$/i) {
  1058.     $ppdfile = $p->{'ppdurl'} if defined($p->{'ppdurl'});
  1059.     }
  1060.  
  1061.     # There is a link to a custom PPD, if it is installed on the local
  1062.     # machine, use the custom PPD instead of generating one from the
  1063.     # Foomatic data
  1064.     if ($ppdfile) {
  1065.     $ppdfile =~ s,^http://.*/(PPD/.*)$,$1,;
  1066.     $ppdfile = $libdir . "/db/source/" . $ppdfile;
  1067.     $ppdfile = "${ppdfile}.gz" if (! -r $ppdfile);
  1068.     if (-r $ppdfile) {
  1069.         $this->getdatfromppd($ppdfile);
  1070.         $this->{'dat'}{'ppdfile'} = $ppdfile;
  1071.         return $this->{'dat'};
  1072.     }
  1073.     }
  1074.  
  1075.     # Generate Perl data structure from database
  1076.     my %dat;            # Our purpose in life...
  1077.     my $VAR1;
  1078.     eval (`$bindir/foomatic-combo-xml -d '$drv' -p '$poid' -l '$libdir' | $bindir/foomatic-perl-data -C -l $this->{'language'}`) ||
  1079.     die ("Could not run \"foomatic-combo-xml\"/" .
  1080.          "\"foomatic-perl-data\"!");
  1081.     %dat = %{$VAR1};
  1082.  
  1083.     # Funky one-at-a-time cache thing
  1084.     $this->{'dat'} = \%dat;
  1085.  
  1086.     # We do some additional stuff which is very awkward to implement in C
  1087.     # now, so we do it here
  1088.  
  1089.     # Some clean-up
  1090.     checklongnames($this->{'dat'});
  1091.     sortoptions($this->{'dat'});
  1092.     generalentries($this->{'dat'});
  1093.     if (defined($this->{'dat'}{'shortdescription'})) {
  1094.     $this->{'dat'}{'shortdescription'} =~ s/[\s\n\r]+/ /s;
  1095.     $this->{'dat'}{'shortdescription'} =~ s/^\s+//;
  1096.     $this->{'dat'}{'shortdescription'} =~ s/\s+$//;
  1097.     }
  1098.     return \%dat;
  1099. }
  1100.  
  1101. sub getdatfromppd {
  1102.  
  1103.     my ($this, $ppdfile, $parameters) = @_;
  1104.  
  1105.     my $dat = ppdtoperl($ppdfile, $parameters);
  1106.     
  1107.     if (!defined($dat)) {
  1108.     die ("Unable to open PPD file \'$ppdfile\'\n");
  1109.     }
  1110.  
  1111.     $this->{'dat'} = $dat;
  1112.  
  1113. }
  1114.  
  1115. sub ppdtoperl {
  1116.  
  1117.     # Build a Perl data structure of the printer/driver options
  1118.  
  1119.     my ($ppdfile, $parameters) = @_;
  1120.  
  1121.     # Load the PPD file and send it to the parser
  1122.     open PPD, ($ppdfile !~ /\.gz$/i ? "< $ppdfile" : 
  1123.            "$sysdeps->{'gzip'} -cd \'$ppdfile\' |") or return undef;
  1124.     my @ppd = <PPD>;
  1125.     close PPD;
  1126.     $parameters->{'ppdfile'} = $ppdfile if $parameters;
  1127.     return ppdfromvartoperl(\@ppd, $parameters);
  1128. }
  1129.  
  1130. sub apply_driver_and_pdl_info {
  1131.  
  1132.     # Find out printer's page description languages and suitable drivers
  1133.  
  1134.     my ($dat, $parameters) = @_;
  1135.  
  1136.     my %drivers;
  1137.     my $pdls;
  1138.     my $ppddlpath;
  1139.     my $ppddrv = $dat->{'driver'};
  1140.     if ($parameters) {
  1141.     if (defined($parameters->{'drivers'})) {
  1142.         foreach my $d (@{$parameters->{'drivers'}}) {
  1143.         $drivers{$d} = 1;
  1144.         }
  1145.         $ppddrv = $parameters->{'drivers'}[0];
  1146.         $dat->{'driver'} = $parameters->{'drivers'}[0] if
  1147.         $parameters->{'drivers'}[0] =~ /^$dat->{'driver'}/;
  1148.     }
  1149.     if ($parameters->{'recommendeddriver'}) {
  1150.         $dat->{'driver'} = $parameters->{'recommendeddriver'};
  1151.     }
  1152.     if (defined($parameters->{'pdls'})) {
  1153.         $pdls = join(",", @{$parameters->{'pdls'}});
  1154.     }
  1155.     if ($parameters->{'ppdfile'} && $parameters->{'ppdlink'}) {
  1156.         my $ppdfile = $parameters->{'ppdfile'};
  1157.         if ($parameters->{'basedir'}) {
  1158.         my $basedir = $parameters->{'basedir'};
  1159.         $basedir =~ s:/+$::;
  1160.         if (! -d $basedir) {
  1161.             die ("PPD base directory $basedir does not exist!\n");
  1162.         }
  1163.         if (! -r $ppdfile) {
  1164.             $ppddlpath = $ppdfile;
  1165.             $ppdfile = $basedir . "/" . $ppdfile;
  1166.             if (! -r $ppdfile) {
  1167.             die ("Given PPD file not found, neither as $ppddlpath nor as $ppdfile!\n");
  1168.             }
  1169.         } else {
  1170.             $ppddlpath = $1 if $ppdfile =~ m:$basedir/(.*)$:;
  1171.         }
  1172.         } else {
  1173.         if (! -r $ppdfile) {
  1174.             die ("Given PPD file $ppdfile not found!\n");
  1175.         }
  1176.         $ppddlpath = $ppdfile;
  1177.         }
  1178.         if ($ppddlpath eq "") {
  1179.         my $mk = $dat->{'id'};
  1180.         $mk =~ s/^([^\-]+)\-.*$/$1/;
  1181.         my $ppd = $ppdfile;
  1182.         $ppd =~ s:^.*/([^/]+):$1:;
  1183.         $ppddlpath = "PPD/$mk/$ppd";    
  1184.         }
  1185.         $ppddlpath =~ s/\.gz$//i;
  1186.     }
  1187.     }
  1188.  
  1189.     if ($dat->{'driver'} =~ /Postscript/i) {
  1190.     $pdls = join(',', ($pdls, "POSTSCRIPT$dat->{'ppdpslevel'}"));
  1191.     } elsif ($dat->{'driver'} =~ /(pxl|pcl[\s\-]?xl)/i) {
  1192.     $pdls = join(',', ($pdls, "PCLXL"));
  1193.     } elsif ($dat->{'driver'} =~ /(ljet4|lj4)/i) {
  1194.     $pdls = join(',', ($pdls, "PCL5e"));
  1195.     } elsif (($dat->{'driver'} =~ /clj/i) && $dat->{'color'}) {
  1196.     $pdls = join(',', ($pdls, "PCL5c"));
  1197.     } elsif ($dat->{'driver'} =~ /(ljet3|lj3)/i) {
  1198.     $pdls = join(',', ($pdls, "PCL5"));
  1199.     } elsif ($dat->{'driver'} =~ /(laserjet|ljet|lj)/i) {
  1200.     $pdls = join(',', ($pdls, "PCL4"));
  1201.     }
  1202.     $pdls = join(',', ($dat->{'general_cmd'}, $pdls)) if 
  1203.     defined($dat->{'general_cmd'});
  1204.     if ($pdls) {
  1205.     for my $l (split(',', $pdls)) {
  1206.         my ($lang, $level) = ('', '');
  1207.         if ($l =~ /\b(PostScript|PS|BR-?Script|KPDL-?)\s*(\d?)\b/i) {
  1208.         $lang = "postscript";
  1209.         $level = $2;
  1210.         } elsif ($l =~ /\b(PDF)\b/i) {
  1211.         $lang = "pdf";
  1212.         } elsif ($l =~ /\b(PCLXL)\b/i) {
  1213.         $lang = "pcl";
  1214.         $level = "6";
  1215.         } elsif ($l =~ /\b(PCL)(\d\S?|)\b/i) {
  1216.         $lang = "pcl";
  1217.         $level = $2;
  1218.         if (!$level) {
  1219.             if ($dat->{'color'}) { 
  1220.             $level = "5c";
  1221.             } else {
  1222.             $level = "5e";
  1223.             }
  1224.         }
  1225.         } elsif ($l =~ /\b(PJL)\b/i) {
  1226.         $dat->{'pjl'} = 1;
  1227.         $dat->{'jcl'} = 1;
  1228.         }
  1229.         if ($lang) {
  1230.         if (!defined($dat->{'languages'})) {
  1231.             $dat->{'languages'} = [];
  1232.         }
  1233.         my $found = 0;
  1234.         foreach my $ll (@{$dat->{'languages'}}) {
  1235.             if ($ll->{'name'} =~ /^$lang$/i) {
  1236.             $ll->{'level'} = $level if $level && 
  1237.                                        ($level gt $ll->{'level'});
  1238.             $found = 1;
  1239.             }
  1240.         }
  1241.         push(@{$dat->{'languages'}},
  1242.              {
  1243.              'name' => $lang,
  1244.              'level' => $level
  1245.              }) if !$found;
  1246.         }
  1247.     }
  1248.     }
  1249.     $drivers{$dat->{'driver'}} = 1;
  1250.     for my $ll (@{$dat->{'languages'}}) {
  1251.     my $lang = $ll->{'name'};
  1252.     my $level = $ll->{'level'};
  1253.     if ($lang =~ /^postscript$/i) {
  1254.         if ($level eq "1") {
  1255.         $drivers{'Postscript1'} = 1;
  1256.         } else {
  1257.         $drivers{'Postscript'} = 1;
  1258.         }
  1259.     } elsif ($lang =~ /^pcl$/i) {
  1260.         if ($level eq "6") {
  1261.         if ($dat->{'color'}) {
  1262.             $drivers{'pxlcolor'} = 1;
  1263.         } else {
  1264.             $drivers{'pxlmono'} = 1;
  1265.             $drivers{'lj5gray'} = 1;
  1266.             $drivers{'lj5mono'} = 1;
  1267.         }
  1268.         } elsif ($level eq "5e") {
  1269.         $drivers{'ljet4d'} = 1;
  1270.         $drivers{'ljet4'} = 1;
  1271.         $drivers{'lj4dith'} = 1;
  1272.         if ($dat->{'make'} =~ /^(HP|Hewlett[\s-]+Packard)$/i) {
  1273.             $drivers{'hplip'} = 1;
  1274.         } else {
  1275.             $drivers{'hpijs-pcl5e'} = 1;
  1276.         }
  1277.         $drivers{'gutenprint'} = 1;
  1278.         } elsif ($level eq "5c") {
  1279.         $drivers{'cljet5'} = 1;
  1280.         if ($dat->{'make'} =~ /^(HP|Hewlett[\s-]+Packard)$/i) {
  1281.             $drivers{'hplip'} = 1;
  1282.         } else {
  1283.             $drivers{'hpijs-pcl5c'} = 1;
  1284.         }
  1285.         } elsif ($level eq "5") {
  1286.         $drivers{'ljet3d'} = 1;
  1287.         $drivers{'ljet3'} = 1;
  1288.         } elsif ($level eq "4") {
  1289.         $drivers{'laserjet'} = 1;
  1290.         $drivers{'ljetplus'} = 1;
  1291.         $drivers{'ljet2p'} = 1;
  1292.         }
  1293.         # PCL printers print also plain text
  1294.         $dat->{'ascii'} = 'us-ascii';
  1295.     }
  1296.     }
  1297.     for my $drv (keys %drivers) {
  1298.     if (!defined($dat->{'drivers'})) {
  1299.         $dat->{'drivers'} = [];
  1300.     }
  1301.     my $found = 0;
  1302.     foreach my $dd (@{$dat->{'drivers'}}) {
  1303.         if (($dd->{'name'} =~ /^$drv$/i) ||
  1304.         ($dd->{'id'} =~ /^$drv$/i)) {
  1305.         $found = 1;
  1306.         }
  1307.         if ($ppddlpath && ($dd->{'id'} =~ /^$ppddrv$/i)) {
  1308.         $dd->{'ppd'} = $ppddlpath;
  1309.         }
  1310.     }
  1311.     push(@{$dat->{'drivers'}},
  1312.          {
  1313.          'name' => $drv,
  1314.          'id' => $drv,
  1315.          ($ppddlpath && ($drv =~ /^$ppddrv$/i) ?
  1316.           ('ppd' => $ppddlpath) : ())
  1317.          }) if !$found;
  1318.     }
  1319. }
  1320.  
  1321. sub ppdfromvartoperl {
  1322.  
  1323.     my ($ppd, $parameters) = @_;
  1324.  
  1325.     # Build a data structure for the renderer's command line and the
  1326.     # options
  1327.  
  1328.     my $dat = {};              # data structure for the options
  1329.     my $currentargument = "";  # We are currently reading this argument
  1330.     my $currentgroup = "";     # We are currently in this group/subgroup
  1331.     my @currentgrouptrans;     # Translation/long name for group/subgroup
  1332.     my $isfoomatic = 0;        # Do we have a Foomatic PPD?
  1333.  
  1334.     # If we have an old Foomatic 2.0.x PPD file, read its built-in Perl
  1335.     # data structure into @datablob and the default values in %ppddefaults
  1336.     # Then delete the $dat structure, replace it by the one "eval"ed from
  1337.     # @datablob, and correct the default settings according to the ones of
  1338.     # the main PPD structure
  1339.     my @datablob;
  1340.     
  1341.     $dat->{"encoding"} = "ascii";
  1342.  
  1343.     # search for LanguageEncoding
  1344.     for (my $i = 0; $i < @{$ppd}; $i ++) {
  1345.     $_ = $ppd->[$i];
  1346.     if (m/^\*LanguageEncoding:\s*(\S+)\s*$/) {
  1347.         # "*LanguageEncoding: <encoding>"        
  1348.         $dat->{'encoding'} = $1;
  1349.         if ($dat->{'encoding'} eq 'MacStandard') {
  1350.         $dat->{'encoding'} = 'MacCentralEurRoman'; 
  1351.         } elsif ($dat->{'encoding'} eq 'WindowsANSI') {
  1352.         $dat->{'encoding'} = 'cp1252'; 
  1353.         } elsif ($dat->{'encoding'} eq 'JIS83-RKSJ') {
  1354.         $dat->{'encoding'} = 'shiftjis';
  1355.         }
  1356.         last;
  1357.     }
  1358.     }
  1359.     # decode PPD
  1360.     my $encoding = $dat->{"encoding"};
  1361.     for (my $i = 0; $i < @{$ppd}; $i ++) {
  1362.     $ppd->[$i] = decode($encoding, $ppd->[$i]);
  1363.     }
  1364.  
  1365.     $dat->{'maxpaperwidth'} = 0;
  1366.  
  1367.     # Parse the PPD file
  1368.     for (my $i = 0; $i < @{$ppd}; $i ++) {
  1369.     $_ = $ppd->[$i];
  1370.     # Foomatic should also work with PPD files downloaded under
  1371.     # Windows.
  1372.     $_ = undossify($_);
  1373.     # Parse keywords
  1374.     if (m!^\*NickName:\s*\"(.*)$!) {
  1375.         # "*NickName: <code>"
  1376.         my $line = $1;
  1377.         # Store the value
  1378.         # Code string can have multiple lines, read all of them
  1379.         my $cmd = "";
  1380.         while ($line !~ m!\"!) {
  1381.         $line =~ s/^\s*//;
  1382.         $line =~ s/\s*$//;
  1383.         $cmd .= " $line";
  1384.         # Read next line
  1385.         $i ++;
  1386.         $line = $ppd->[$i];
  1387.         chomp $line;
  1388.         }
  1389.         $line =~ s/^\s*//;
  1390.         $line =~ m!^([^\"]*?)\s*\"!;
  1391.         $cmd .= " $1";
  1392.         $cmd =~ s/^\s*//;
  1393.         $dat->{'makemodel'} = unhexify($cmd);
  1394.         $dat->{'makemodel'} =~ s/^([^,]+),.*$/$1/;
  1395.     } elsif (m!^\*ModelName:\s*\"(.*)$!) {
  1396.         # "*ModelName: <code>"
  1397.         my $line = $1;
  1398.         # Store the value
  1399.         # Code string can have multiple lines, read all of them
  1400.         my $cmd = "";
  1401.         while ($line !~ m!\"!) {
  1402.         $line =~ s/^\s*//;
  1403.         $line =~ s/\s*$//;
  1404.         $cmd .= " $line";
  1405.         # Read next line
  1406.         $i ++;
  1407.         $line = $ppd->[$i];
  1408.         chomp $line;
  1409.         }
  1410.         $line =~ s/^\s*//;
  1411.         $line =~ m!^([^\"]*?)\s*\"!;
  1412.         $cmd .= " $1";
  1413.         $cmd =~ s/^\s*//;
  1414.         $dat->{'ppdmodelname'} = unhexify($cmd);
  1415.     } elsif (m!^\*Product:\s*\"(.*)$!) {
  1416.         # "*Product: <code>"
  1417.         my $line = $1;
  1418.         # Store the value
  1419.         # Code string can have multiple lines, read all of them
  1420.         my $cmd = "";
  1421.         while ($line !~ m!\"!) {
  1422.         $line =~ s/^\s*//;
  1423.         $line =~ s/\s*$//;
  1424.         $cmd .= " $line";
  1425.         # Read next line
  1426.         $i ++;
  1427.         $line = $ppd->[$i];
  1428.         chomp $line;
  1429.         }
  1430.         $line =~ s/^\s*//;
  1431.         $line =~ m!^([^\"]*?)\s*\"!;
  1432.         $cmd .= " $1";
  1433.         $cmd =~ s/^\s*//;
  1434.         my $ppdproduct = unhexify($cmd);
  1435.         $ppdproduct =~ s/^\s*\(\s*//;
  1436.         $ppdproduct =~ s/\s*\)\s*$//;
  1437.         @{$dat->{'ppdproduct'}} = ()
  1438.         if !defined($dat->{'ppdproduct'});
  1439.         push(@{$dat->{'ppdproduct'}}, $ppdproduct);
  1440.     } elsif (m!^\*Manufacturer:\s*\"(.*)$!) {
  1441.         # "*Manufacturer: <code>"
  1442.         my $line = $1;
  1443.         # Store the value
  1444.         # Code string can have multiple lines, read all of them
  1445.         my $cmd = "";
  1446.         while ($line !~ m!\"!) {
  1447.         $line =~ s/^\s*//;
  1448.         $line =~ s/\s*$//;
  1449.         $cmd .= " $line";
  1450.         # Read next line
  1451.         $i ++;
  1452.         $line = $ppd->[$i];
  1453.         chomp $line;
  1454.         }
  1455.         $line =~ s/^\s*//;
  1456.         $line =~ m!^([^\"]*?)\s*\"!;
  1457.         $cmd .= " $1";
  1458.         $cmd =~ s/^\s*//;
  1459.         $dat->{'ppdmanufacturer'} = unhexify($cmd);
  1460.     } elsif (m!^\*LanguageVersion:\s*(\S+)\s*$!) {
  1461.         # "*LanguageVersion: <language>"
  1462.         $dat->{'language'} = $1;
  1463.     } elsif (m!^\*ColorDevice:\s*(\S+)\s*$!) {
  1464.         # "*ColorDevice: <boolean>"
  1465.         my $col = $1;
  1466.         if ($col =~ /true/i) { 
  1467.         $dat->{'color'} = 1;
  1468.         } elsif ($col =~ /false/i) { 
  1469.         $dat->{'color'} = 0;
  1470.         }
  1471.     } elsif (m!^\*LanguageLevel:\s*\"?(\S+?)\"?\s*$!) {
  1472.         # "*LanguageLevel: "<level>""
  1473.         $dat->{'ppdpslevel'} = $1;
  1474.     } elsif (m!^\*Throughput:\s*\"?(\S+?)\"?\s*$!) {
  1475.         # "*Throughput: "<pages/min>""
  1476.         $dat->{'throughput'} = $1;
  1477.     } elsif (m!^\*1284DeviceID:\s*\"(.*)$!) {
  1478.         # "*1284DeviceID: <code>"
  1479.         my $line = $1;
  1480.         # Store the value
  1481.         # Code string can have multiple lines, read all of them
  1482.         my $cmd = "";
  1483.         while ($line !~ m!\"!) {
  1484.         $line =~ s/^\s*//;
  1485.         $line =~ s/\s*$//;
  1486.         $cmd .= $line;
  1487.         # Read next line
  1488.         $i ++;
  1489.         $line = $ppd->[$i];
  1490.         chomp $line;
  1491.         }
  1492.         $line =~ m!^([^\"]*?)\s*\"!;
  1493.         $cmd .= $1;
  1494.         $cmd =~ s/^\s*//;
  1495.         if (!defined($dat->{'general_ieee'}) ||
  1496.         (length($dat->{'general_ieee'}) <
  1497.          length($cmd))) {
  1498.         $dat->{'general_ieee'} = unhexify($cmd);
  1499.         if ($dat->{'general_ieee'} =~ /(MFG|MANUFACTURER):\s*([^:;]+);?/i) {
  1500.             $dat->{'general_mfg'} = $2;
  1501.         }
  1502.         if ($dat->{'general_ieee'} =~ /(MDL|MODEL):\s*([^:;]+);?/i) {
  1503.             $dat->{'general_mdl'} = $2;
  1504.         }
  1505.         if ($dat->{'general_ieee'} =~ /(CMD|COMMANDS?\s*SET):\s*([^:;]+);?/i) {
  1506.             $dat->{'general_cmd'} = $2;
  1507.         }
  1508.         if ($dat->{'general_ieee'} =~ /(DES|DESCRIPTION):\s*([^:;]+);?/i) {
  1509.             $dat->{'general_des'} = $2;
  1510.         }
  1511.         }
  1512.     } elsif (m!^\*PaperDimension\s+([^:]+):\s*\"(.*)$!) {
  1513.         # "*PaperDimension <format>: <code>"
  1514.         my $line = $2;
  1515.         # Store the value
  1516.         # Code string can have multiple lines, read all of them
  1517.         my $cmd = "";
  1518.         while ($line !~ m!\"!) {
  1519.         $line =~ s/^\s*//;
  1520.         $line =~ s/\s*$//;
  1521.         $cmd .= " $line";
  1522.         # Read next line
  1523.         $i ++;
  1524.         $line = $ppd->[$i];
  1525.         chomp $line;
  1526.         }
  1527.         $line =~ s/^\s*//;
  1528.         $line =~ m!^([^\"]*?)\s*\"!;
  1529.         $cmd .= " $1";
  1530.         $cmd =~ s/^\s*//;
  1531.         $cmd =~ /^(\d+)/;
  1532.         my $width = $1;
  1533.         $dat->{'maxpaperwidth'} = $width if 
  1534.         $width && ($width > $dat->{'maxpaperwidth'});
  1535.     } elsif (m!^\*cupsFilter\s+([^:]+):\s*\"(.*)$!) {
  1536.         # "*cupsFilter: <code>"
  1537.         my $line = $2;
  1538.         # Store the value
  1539.         # Code string can have multiple lines, read all of them
  1540.         my $cmd = "";
  1541.         while ($line !~ m!\"!) {
  1542.         $line =~ s/^\s*//;
  1543.         $line =~ s/\s*$//;
  1544.         $cmd .= " $line";
  1545.         # Read next line
  1546.         $i ++;
  1547.         $line = $ppd->[$i];
  1548.         chomp $line;
  1549.         }
  1550.         $line =~ s/^\s*//;
  1551.         $line =~ m!^([^\"]*?)\s*\"!;
  1552.         $cmd .= " $1";
  1553.         $cmd =~ s/^\s*//;
  1554.         push(@{$dat->{'cupsfilterlines'}}, $cmd);
  1555.     } elsif (m!^\*FoomaticIDs:\s*(\S+)\s+(\S+)\s*$!) {
  1556.         # "*FoomaticIDs: <printer ID> <driver ID>"
  1557.         my $id = $1;
  1558.         my $driver = $2;
  1559.         # Store the values
  1560.         $dat->{'id'} = $id;
  1561.         $dat->{'driver'} = $driver;
  1562.         $isfoomatic = 1;
  1563.     } elsif (m!^\*FoomaticRIPPostPipe:\s*\"(.*)$!) {
  1564.         # "*FoomaticRIPPostPipe: <code>"
  1565.         my $line = $1;
  1566.         # Store the value
  1567.         # Code string can have multiple lines, read all of them
  1568.         my $cmd = "";
  1569.         while ($line !~ m!\"!) {
  1570.         if ($line =~ m!&&$!) {
  1571.             # line continues in next line
  1572.             $cmd .= substr($line, 0, -2);
  1573.         } else {
  1574.             # line ends here
  1575.             $cmd .= "$line\n";
  1576.         }
  1577.         # Read next line
  1578.         $i ++;
  1579.         $line = $ppd->[$i];
  1580.         chomp $line;
  1581.         }
  1582.         $line =~ m!^([^\"]*)\"!;
  1583.         $cmd .= $1;
  1584.         $dat->{'postpipe'} = unhtmlify($cmd);
  1585.     } elsif (m!^\*FoomaticRIPCommandLine:\s*\"(.*)$!) {
  1586.         # "*FoomaticRIPCommandLine: <code>"
  1587.         my $line = $1;
  1588.         # Store the value
  1589.         # Code string can have multiple lines, read all of them
  1590.         my $cmd = "";
  1591.         while ($line !~ m!\"!) {
  1592.         if ($line =~ m!&&$!) {
  1593.             # line continues in next line
  1594.             $cmd .= substr($line, 0, -2);
  1595.         } else {
  1596.             # line ends here
  1597.             $cmd .= "$line\n";
  1598.         }
  1599.         # Read next line
  1600.         $i ++;
  1601.         $line = $ppd->[$i];
  1602.         chomp $line;
  1603.         }
  1604.         $line =~ m!^([^\"]*)\"!;
  1605.         $cmd .= $1;
  1606.         $dat->{'cmd'} = unhtmlify($cmd);
  1607.     } elsif (m!^\*FoomaticRIPCommandLinePDF:\s*\"(.*)$!) {
  1608.         # "*FoomaticRIPCommandLinePDF: <code>"
  1609.         my $line = $1;
  1610.         # Store the value
  1611.         # Code string can have multiple lines, read all of them
  1612.         my $cmd = "";
  1613.         while ($line !~ m!\"!) {
  1614.         if ($line =~ m!&&$!) {
  1615.             # line continues in next line
  1616.             $cmd .= substr($line, 0, -2);
  1617.         } else {
  1618.             # line ends here
  1619.             $cmd .= "$line\n";
  1620.         }
  1621.         # Read next line
  1622.         $i ++;
  1623.         $line = $ppd->[$i];
  1624.         chomp $line;
  1625.         }
  1626.         $line =~ m!^([^\"]*)\"!;
  1627.         $cmd .= $1;
  1628.         $dat->{'cmd_pdf'} = unhtmlify($cmd);
  1629.     } elsif (m!^\*FoomaticRIPNoPageAccounting:\s*(\S+)\s*$!) {
  1630.         # "*FoomaticRIPNoPageAccounting: <boolean value>"
  1631.         my $value = $1;
  1632.         # Store the value
  1633.         if ($value =~ /^True$/i) {
  1634.         $dat->{'drivernopageaccounting'} = 1;
  1635.         } else {
  1636.         delete $dat->{'drivernopageaccounting'};
  1637.         }
  1638.     } elsif (m!^\*CustomPageSize\s+True:\s*\"(.*)$!) {
  1639.         # "*CustomPageSize True: <code>"
  1640.         my $setting = "Custom";
  1641.         my $translation = "Custom Size";
  1642.         my $line = $1;
  1643.         # Make sure that the argument is in the data structure
  1644.         checkarg ($dat, "PageSize");
  1645.         checkarg ($dat, "PageRegion");
  1646.         # "PageSize" and "PageRegion" must be both user-visible as they are
  1647.         # options required by the PPD spec
  1648.         undef $dat->{'args_byname'}{"PageSize"}{'hidden'};
  1649.         undef $dat->{'args_byname'}{"PageRegion"}{'hidden'};
  1650.         # Make sure that the setting is in the data structure
  1651.         checksetting ($dat, "PageSize", $setting);
  1652.         checksetting ($dat, "PageRegion", $setting);
  1653.         $dat->{'args_byname'}{'PageSize'}{'vals_byname'}{$setting}{'comment'} = $translation;
  1654.         $dat->{'args_byname'}{'PageRegion'}{'vals_byname'}{$setting}{'comment'} = $translation;
  1655.         # Store the value
  1656.         # Code string can have multiple lines, read all of them
  1657.         my $code = "";
  1658.         while ($line !~ m!\"!) {
  1659.         if ($line =~ m!&&$!) {
  1660.             # line continues in next line
  1661.             $code .= substr($line, 0, -2);
  1662.         } else {
  1663.             # line ends here
  1664.             $code .= "$line\n";
  1665.         }
  1666.         # Read next line
  1667.         $i ++;
  1668.         $line = $ppd->[$i];
  1669.         chomp $line;
  1670.         }
  1671.         $line =~ m!^([^\"]*)\"!;
  1672.         $code .= $1;
  1673.         if ($code !~ m!^%% FoomaticRIPOptionSetting!m) {
  1674.         $dat->{'args_byname'}{'PageSize'}{'vals_byname'}{$setting}{'driverval'} = $code;
  1675.         $dat->{'args_byname'}{'PageRegion'}{'vals_byname'}{$setting}{'driverval'} = $code;
  1676.         }
  1677.     } elsif (m!^\*Open(Sub|)Group:\s*\*?([^/]+?)(/(.*)|)$!) {
  1678.         # "*Open[Sub]Group: <group>[/<translation>]
  1679.         my $group = $2;
  1680.         chomp($group) if $group;
  1681.         my $grouptrans = $4;
  1682.         chomp($grouptrans) if $grouptrans;
  1683.         if (!$grouptrans) {
  1684.         $grouptrans = longname($group);
  1685.         }
  1686.         if ($currentgroup) {
  1687.         $currentgroup .= "/";
  1688.         }
  1689.         $currentgroup .= $group;
  1690.         push(@currentgrouptrans, 
  1691.          unhexify($grouptrans, $dat->{"encoding"}));
  1692.     } elsif (m!^\*Close(Sub|)Group:?\s*\*?([^/]+?)$!) {
  1693.         # "*Close[Sub]Group: <group>"
  1694.         my $group = $2;
  1695.         chomp($group) if $group;
  1696.         $currentgroup =~ s!$group$!!;
  1697.         $currentgroup =~ s!/$!!;
  1698.         pop(@currentgrouptrans);
  1699.     } elsif (m!^\*Close(Sub|)Group\s*$!) {
  1700.         # "*Close[Sub]Group"
  1701.         # NOTE: This expression is not Adobe-conforming
  1702.         $currentgroup =~ s![^/]+$!!;
  1703.         $currentgroup =~ s!/$!!;
  1704.         pop(@currentgrouptrans);
  1705.     } elsif (m!^\*(JCL|)OpenUI\s+\*([^:]+):\s*(\S+)\s*$!) {
  1706.         # "*[JCL]OpenUI *<option>[/<translation>]: <type>"
  1707.         my $argnametrans = $2;
  1708.         my $argtype = $3;
  1709.         my $argname;
  1710.         my $translation = "";
  1711.         if ($argnametrans =~ m!^([^:/\s]+)/([^:]*)$!) {
  1712.         $argname = $1;
  1713.         $translation = $2;
  1714.         } else {
  1715.         $argname = $argnametrans;
  1716.         }
  1717.         # Make sure that the argument is in the data structure
  1718.         checkarg ($dat, $argname);
  1719.         # This option has a non-Foomatic keyword, so this is not
  1720.         # a hidden option
  1721.         undef $dat->{'args_byname'}{$argname}{'hidden'};
  1722.         # Store the values
  1723.         $dat->{'args_byname'}{$argname}{'comment'} = 
  1724.         unhexify($translation, $dat->{"encoding"});
  1725.         $dat->{'args_byname'}{$argname}{'group'} = $currentgroup;
  1726.         @{$dat->{'args_byname'}{$argname}{'grouptrans'}} =
  1727.         @currentgrouptrans;
  1728.         # Set the argument type only if not defined yet, a
  1729.         # definition in "*FoomaticRIPOption" has priority
  1730.         if (!defined($dat->{'args_byname'}{$argname}{'type'})) {
  1731.         if ($argtype eq "PickOne") {
  1732.             $dat->{'args_byname'}{$argname}{'type'} = 'enum';
  1733.         } elsif ($argtype eq "PickMany") {
  1734.             $dat->{'args_byname'}{$argname}{'type'} = 'pickmany';
  1735.         } elsif ($argtype eq "Boolean") {
  1736.             $dat->{'args_byname'}{$argname}{'type'} = 'bool';
  1737.         }
  1738.         }
  1739.         # Mark in which argument we are currently, so that we can find
  1740.         # the entries for the choices
  1741.         $currentargument = $argname;
  1742.     } elsif (m!^\*(JCL|)CloseUI:?\s+\*([^:/\s]+)\s*$!) {
  1743.         next if !$currentargument;
  1744.         # "*[JCL]CloseUI: *<option>"
  1745.         my $argname = $2;
  1746.         # Unmark the current argument to do not mis-interpret any 
  1747.         # keywords as choices
  1748.         $currentargument = "";
  1749.     } elsif ((m!^\*FoomaticRIPOption ([^/:\s]+):\s*(\S+)\s+(\S+)\s+(\S)\s*$!) ||
  1750.          (m!^\*FoomaticRIPOption ([^/:\s]+):\s*(\S+)\s+(\S+)\s+(\S)\s+(\S+)\s*$!)){
  1751.         # "*FoomaticRIPOption <option>: <type> <style> <spot> [<order>]"
  1752.         # <order> only used for 1-choice enum options
  1753.         my $argname = $1;
  1754.         my $argtype = $2;
  1755.         my $argstyle = $3;
  1756.         my $spot = $4;
  1757.         my $order = $5;
  1758.         # Make sure that the argument is in the data structure
  1759.         checkarg ($dat, $argname);
  1760.         # Store the values
  1761.         $dat->{'args_byname'}{$argname}{'type'} = $argtype;
  1762.         if ($argstyle eq "PS") {
  1763.         $dat->{'args_byname'}{$argname}{'style'} = 'G';
  1764.         } elsif ($argstyle eq "CmdLine") {
  1765.         $dat->{'args_byname'}{$argname}{'style'} = 'C';
  1766.         } elsif ($argstyle eq "JCL") {
  1767.         $dat->{'args_byname'}{$argname}{'style'} = 'J';
  1768.         $dat->{'jcl'} = 1;
  1769.         $dat->{'pjl'} = 1;
  1770.         } elsif ($argstyle eq "Composite") {
  1771.         $dat->{'args_byname'}{$argname}{'style'} = 'X';
  1772.         }
  1773.         $dat->{'args_byname'}{$argname}{'spot'} = $spot;
  1774.         # $order only defined here for 1-choice enum options
  1775.         if ($order) {
  1776.         $dat->{'args_byname'}{$argname}{'order'} = $order;
  1777.         }
  1778.     } elsif (m!^\*FoomaticRIPOptionPrototype\s+([^/:\s]+):\s*\"(.*)$!) {
  1779.         # "*FoomaticRIPOptionPrototype <option>: <code>"
  1780.         # Used for numerical and string options only
  1781.         my $argname = $1;
  1782.         my $line = $2;
  1783.         # Make sure that the argument is in the data structure
  1784.         checkarg ($dat, $argname);
  1785.         # Store the value
  1786.         # Code string can have multiple lines, read all of them
  1787.         my $proto = "";
  1788.         while ($line !~ m!\"!) {
  1789.         if ($line =~ m!&&$!) {
  1790.             # line continues in next line
  1791.             $proto .= substr($line, 0, -2);
  1792.         } else {
  1793.             # line ends here
  1794.             $proto .= "$line\n";
  1795.         }
  1796.         # Read next line
  1797.         $i ++;
  1798.         $line = $ppd->[$i];
  1799.         chomp $line;
  1800.         }
  1801.         $line =~ m!^([^\"]*)\"!;
  1802.         $proto .= $1;
  1803.         $dat->{'args_byname'}{$argname}{'proto'} = unhtmlify($proto);
  1804.     } elsif (m!^\*FoomaticRIPOptionRange\s+([^/:\s]+):\s*(\S+)\s+(\S+)\s*$!) {
  1805.         # "*FoomaticRIPOptionRange <option>: <min> <max>"
  1806.         # Used for numerical options only
  1807.         my $argname = $1;
  1808.         my $min = $2;
  1809.         my $max = $3;
  1810.         # Make sure that the argument is in the data structure
  1811.         checkarg ($dat, $argname);
  1812.         # Store the values
  1813.         $dat->{'args_byname'}{$argname}{'min'} = $min;
  1814.         $dat->{'args_byname'}{$argname}{'max'} = $max;
  1815.     } elsif (m!^\*FoomaticRIPOptionMaxLength\s+([^/:\s]+):\s*(\S+)\s*$!) {
  1816.         # "*FoomaticRIPOptionMaxLength <option>: <length>"
  1817.         # Used for string options only
  1818.         my $argname = $1;
  1819.         my $maxlength = $2;
  1820.         # Make sure that the argument is in the data structure
  1821.         checkarg ($dat, $argname);
  1822.         # Store the value
  1823.         $dat->{'args_byname'}{$argname}{'maxlength'} = $maxlength;
  1824.     } elsif (m!^\*FoomaticRIPOptionAllowedChars\s+([^/:\s]+):\s*\"(.*)$!) {
  1825.         # "*FoomaticRIPOptionAllowedChars <option>: <code>"
  1826.         # Used for string options only
  1827.         my $argname = $1;
  1828.         my $line = $2;
  1829.         # Store the value
  1830.         # Code string can have multiple lines, read all of them
  1831.         my $code = "";
  1832.         while ($line !~ m!\"!) {
  1833.         if ($line =~ m!&&$!) {
  1834.             # line continues in next line
  1835.             $code .= substr($line, 0, -2);
  1836.         } else {
  1837.             # line ends here
  1838.             $code .= "$line\n";
  1839.         }
  1840.         # Read next line
  1841.         $i ++;
  1842.         $line = $ppd->[$i];
  1843.         chomp $line;
  1844.         }
  1845.         $line =~ m!^([^\"]*)\"!;
  1846.         $code .= $1;
  1847.         # Make sure that the argument is in the data structure
  1848.         checkarg ($dat, $argname);
  1849.         # Store the value
  1850.         $dat->{'args_byname'}{$argname}{'allowedchars'} = unhtmlify($code);
  1851.     } elsif (m!^\*FoomaticRIPOptionAllowedRegExp\s+([^/:\s]+):\s*\"(.*)$!) {
  1852.         # "*FoomaticRIPOptionAllowedRegExp <option>: <code>"
  1853.         # Used for string options only
  1854.         my $argname = $1;
  1855.         my $line = $2;
  1856.         # Store the value
  1857.         # Code string can have multiple lines, read all of them
  1858.         my $code = "";
  1859.         while ($line !~ m!\"!) {
  1860.         if ($line =~ m!&&$!) {
  1861.             # line continues in next line
  1862.             $code .= substr($line, 0, -2);
  1863.         } else {
  1864.             # line ends here
  1865.             $code .= "$line\n";
  1866.         }
  1867.         # Read next line
  1868.         $i ++;
  1869.         $line = $ppd->[$i];
  1870.         chomp $line;
  1871.         }
  1872.         $line =~ m!^([^\"]*)\"!;
  1873.         $code .= $1;
  1874.         # Make sure that the argument is in the data structure
  1875.         checkarg ($dat, $argname);
  1876.         # Store the value
  1877.         $dat->{'args_byname'}{$argname}{'allowedregexp'} =
  1878.         unhtmlify($code);
  1879.     } elsif (m!^\*OrderDependency:\s*(\S+)\s+(\S+)\s+\*([^:/\s]+)\s*$!) {
  1880.         next if !$currentargument;
  1881.         # "*OrderDependency: <order> <section> *<option>"
  1882.         my $order = $1;
  1883.         my $section = $2;
  1884.         my $argname = $3;
  1885.         # Make sure that the argument is in the data structure
  1886.         checkarg ($dat, $argname);
  1887.         # This option has a non-Foomatic keyword, so this is not
  1888.         # a hidden option
  1889.         undef $dat->{'args_byname'}{$argname}{'hidden'};
  1890.         # Store the values
  1891.         $dat->{'args_byname'}{$argname}{'order'} = $order;
  1892.         $dat->{'args_byname'}{$argname}{'section'} = $section;
  1893.     } elsif (m!^\*Default([^/:\s]+):\s*([^/:\s]+)\s*$!) {
  1894.         # "*Default<option>: <value>"
  1895.         my $argname = $1;
  1896.         my $default = $2;
  1897.         # Make sure that the argument is in the data structure
  1898.         checkarg ($dat, $argname);
  1899.         # Store the value
  1900.         $dat->{'args_byname'}{$argname}{'default'} = $default;
  1901.     } elsif (m!^\*FoomaticRIPDefault([^/:\s]+):\s*([^/:\s]+)\s*$!) {
  1902.         # "*FoomaticRIPDefault<option>: <value>"
  1903.         # Used for numerical options only
  1904.         my $argname = $1;
  1905.         my $default = $2;
  1906.         # Make sure that the argument is in the data structure
  1907.         checkarg ($dat, $argname);
  1908.         # Store the value
  1909.         $dat->{'args_byname'}{$argname}{'fdefault'} = $default;
  1910.     } elsif (m!^\*$currentargument\s+([^:]+):\s*\"(.*)$!) {
  1911.         next if !$currentargument;
  1912.         # "*<option> <choice>[/<translation>]: <code>"
  1913.         my $settingtrans = $1;
  1914.         my $line = $2;
  1915.         my $translation = "";
  1916.         my $setting = "";
  1917.         if ($settingtrans =~ m!^([^:/\s]+)/([^:]*)$!) {
  1918.         $setting = $1;
  1919.         $translation = $2;
  1920.         } else {
  1921.         $setting = $settingtrans;
  1922.         }
  1923.         $translation = unhexify($translation, $dat->{"encoding"});
  1924.         # Make sure that the argument is in the data structure
  1925.         checkarg ($dat, $currentargument);
  1926.         # This option has a non-Foomatic keyword, so this is not
  1927.         # a hidden option
  1928.         undef $dat->{'args_byname'}{$currentargument}{'hidden'};
  1929.         # Make sure that the setting is in the data structure (enum
  1930.         # options)
  1931.         my $bool =
  1932.         ($dat->{'args_byname'}{$currentargument}{'type'} eq 'bool');
  1933.         if ($bool) {
  1934.         if (lc($setting) eq "true") {
  1935.             if (!$dat->{'args_byname'}{$currentargument}{'comment'}) {
  1936.             $dat->{'args_byname'}{$currentargument}{'comment'} =
  1937.                 $translation;
  1938.             }
  1939.             $dat->{'args_byname'}{$currentargument}{'comment_true'} =
  1940.             $translation;
  1941.         } else {
  1942.             $dat->{'args_byname'}{$currentargument}{'comment_false'} =
  1943.             $translation;
  1944.         }
  1945.         } else {
  1946.         checksetting ($dat, $currentargument, $setting);
  1947.         $dat->{'args_byname'}{$currentargument}{'vals_byname'}{$setting}{'comment'} = $translation;
  1948.         # Make sure that this argument has a default setting, even
  1949.         # if none is defined in this PPD file
  1950.         if (!defined($dat->{'args_byname'}{$currentargument}{'default'}) ||
  1951.             ($dat->{'args_byname'}{$currentargument}{'default'} eq "")) {
  1952.             $dat->{'args_byname'}{$currentargument}{'default'} = $setting;
  1953.         }
  1954.         }
  1955.         # Store the value
  1956.         # Code string can have multiple lines, read all of them
  1957.         my $code = "";
  1958.         while ($line !~ m!\"!) {
  1959.         if ($line =~ m!&&$!) {
  1960.             # line continues in next line
  1961.             $code .= substr($line, 0, -2);
  1962.         } else {
  1963.             # line ends here
  1964.             $code .= "$line\n";
  1965.         }
  1966.         # Read next line
  1967.         $i ++;
  1968.         $line = $ppd->[$i];
  1969.         chomp $line;
  1970.         }
  1971.         $line =~ m!^([^\"]*)\"!;
  1972.         $code .= $1;
  1973.         if ($code !~ m!^%% FoomaticRIPOptionSetting!) {
  1974.         if ($bool) {
  1975.             if (lc($setting) eq "true") {
  1976.             $dat->{'args_byname'}{$currentargument}{'proto'} =
  1977.                 $code;
  1978.             } else {
  1979.             $dat->{'args_byname'}{$currentargument}{'protof'} =
  1980.                 $code;
  1981.             }
  1982.         } else {
  1983.             $dat->{'args_byname'}{$currentargument}{'vals_byname'}{$setting}{'driverval'} = $code;
  1984.         }
  1985.         }
  1986.     } elsif ((m!^\*FoomaticRIPOptionSetting\s+([^/:=\s]+)=([^/:=\s]+):\s*\"(.*)$!) ||
  1987.          (m!^\*FoomaticRIPOptionSetting\s+([^/:=\s]+):\s*\"(.*)$!)) {
  1988.         # "*FoomaticRIPOptionSetting <option>[=<choice>]: <code>"
  1989.         # For boolean options <choice> is not given
  1990.         my $argname = $1;
  1991.         my $setting = $2;
  1992.         my $line = $3;
  1993.         my $bool = 0;
  1994.         if (!$line) {
  1995.         $line = $setting;
  1996.         $bool = 1;
  1997.         }
  1998.         # Make sure that the argument is in the data structure
  1999.         checkarg ($dat, $argname);
  2000.         # Make sure that the setting is in the data structure (enum
  2001.         # options)
  2002.         if (!$bool) {
  2003.         checksetting ($dat, $argname, $setting);
  2004.         # Make sure that this argument has a default setting, even
  2005.         # if none is defined in this PPD file
  2006.         if (!$dat->{'args_byname'}{$argname}{'default'}) {
  2007.             $dat->{'args_byname'}{$argname}{'default'} = $setting;
  2008.         }
  2009.         }
  2010.         # Store the value
  2011.         # Code string can have multiple lines, read all of them
  2012.         my $code = "";
  2013.         while ($line !~ m!\"!) {
  2014.         if ($line =~ m!&&$!) {
  2015.             # line continues in next line
  2016.             $code .= substr($line, 0, -2);
  2017.         } else {
  2018.             # line ends here
  2019.             $code .= "$line\n";
  2020.         }
  2021.         # Read next line
  2022.         $i ++;
  2023.         $line = $ppd->[$i];
  2024.         chomp $line;
  2025.         }
  2026.         $line =~ m!^([^\"]*)\"!;
  2027.         $code .= $1;
  2028.         if ($bool) {
  2029.         $dat->{'args_byname'}{$argname}{'proto'} = unhtmlify($code);
  2030.         } else {
  2031.         $dat->{'args_byname'}{$argname}{'vals_byname'}{$setting}{'driverval'} = unhtmlify($code);
  2032.         }
  2033.     } elsif (m!^\*JCL(Begin|ToPSInterpreter|End):\s*\"(.*)$!) {
  2034.         # "*JCL(Begin|ToPSInterpreter|End): <code>"
  2035.         # The printer supports PJL/JCL when there is such a line 
  2036.         $dat->{'jcl'} = 1;
  2037.         $dat->{'pjl'} = 1;
  2038.         my $item = $1;
  2039.         my $line = $2;
  2040.         # Store the value
  2041.         # Code string can have multiple lines, read all of them
  2042.         my $code = "";
  2043.         while ($line !~ m!\"!) {
  2044.         if ($line =~ m!&&$!) {
  2045.             # line continues in next line
  2046.             $code .= substr($line, 0, -2);
  2047.         } else {
  2048.             # line ends here
  2049.             $code .= "$line\n";
  2050.         }
  2051.         # Read next line
  2052.         $i ++;
  2053.         $line = $ppd->[$i];
  2054.         chomp $line;
  2055.         }
  2056.         $line =~ m!^([^\"]*)\"!;
  2057.         $code .= $1;
  2058.         $code = unhexify($code, $dat->{"encoding"});
  2059.         if ($item eq 'Begin') {
  2060.         $dat->{'jclbegin'} = $code;
  2061.         } elsif ($item eq 'ToPSInterpreter') {
  2062.         $dat->{'jcltointerpreter'} = $code;
  2063.         } elsif ($item eq 'End') {
  2064.         $dat->{'jclend'} = $code;
  2065.         }
  2066.     } elsif (m!^\*\% COMDATA \#(.*)$!) {
  2067.         # If we have an old Foomatic 2.0.x PPD file, collect its Perl 
  2068.         # data
  2069.         push (@datablob, $1);
  2070.     #} elsif (m!(laser|toner)!i) {
  2071.     #    $dat->{'type'} = "laser";
  2072.     #} elsif (m!(ink|nozzle)!i) {
  2073.     #    $dat->{'type'} ||= "inkjet";
  2074.     }
  2075.     }
  2076.  
  2077.     # If we have an old Foomatic 2.0.x PPD file use its Perl data structure
  2078.     if ($#datablob >= 0) {
  2079.     my $VAR1;
  2080.     if (eval join('',@datablob)) {
  2081.         # Overtake default settings from the main structure of the
  2082.         # PPD file
  2083.         for my $arg (@{$dat->{'args'}}) {
  2084.         if ($arg->{'default'}) {
  2085.             $VAR1->{'argsbyname'}{$arg->{'name'}}{'default'} = 
  2086.             $arg->{'default'};
  2087.         }
  2088.         }
  2089.         undef $dat;
  2090.         $dat = $VAR1;
  2091.         $dat->{'jcl'} = $dat->{'pjl'};
  2092.         $isfoomatic = 1;
  2093.     } else {
  2094.         # Perl structure broken
  2095.         warn "\nUnable to evaluate datablob, print jobs may come " .
  2096.         "out incorrectly or not at all.\n\n";
  2097.     }
  2098.     }
  2099.  
  2100.     # Set manufacturer and model fields
  2101.     if (defined($dat->{'ppdmanufacturer'})) {
  2102.     $dat->{'make'} = $dat->{'ppdmanufacturer'};
  2103.     } elsif (defined($dat->{'general_mfg'})) {
  2104.     $dat->{'make'} = $dat->{'general_mfg'};
  2105.     } elsif (defined($dat->{'makemodel'})) {
  2106.     ($dat->{'make'}, $dat->{'model'}) = guessmake($dat->{'makemodel'});
  2107.     $dat->{'model'} =~ s/^(.*?)\s*(,|Foomatic|CUPS|\(?\d+\.\d+\)?)/$1/i;
  2108.     }
  2109.     if (defined($dat->{'ppdmodelname'})) {
  2110.     (my $dummy, $dat->{'model'}) = guessmake($dat->{'ppdmodelname'});
  2111.     } elsif (defined($dat->{'ppdproduct'}) &&
  2112.          (scaler(@{$dat->{'ppdproduct'}}) == 1)) {
  2113.     $dat->{'model'} = $dat->{'ppdproduct'}[0];
  2114.     } elsif (!$dat->{'model'} && defined($dat->{'general_mdl'})) {
  2115.     $dat->{'model'} = $dat->{'general_mdl'};
  2116.     } elsif (defined($dat->{'ppdproduct'})) {
  2117.     $dat->{'model'} = $dat->{'ppdproduct'}[0];
  2118.     }
  2119.     $dat->{'make'} = clean_manufacturer_name($dat->{'make'});
  2120.     $dat->{'model'} = clean_manufacturer_name($dat->{'model'});
  2121.     ($dat->{'make'}, $dat->{'model'}) = guessmake($dat->{'model'})
  2122.     if !$dat->{'make'};
  2123.     $dat->{'model'} =~ s/^\s*$dat->{'make'}\s+//i;
  2124.     $dat->{'model'} = clean_model_name($dat->{'model'});
  2125.  
  2126.     # Generate a device ID if none was supplied. The PPD specs
  2127.     # expect the make and model of the device ID in the *Manufacturer
  2128.     # and *Product fields of the PPD.
  2129.     $dat->{'general_mfg'} = $dat->{'ppdmanufacturer'} if 
  2130.     $dat->{'ppdmanufacturer'} && !$dat->{'general_mfg'};
  2131.     $dat->{'general_mdl'} = $dat->{'ppdproduct'}[0] if 
  2132.     $dat->{'ppdproduct'} && !$dat->{'general_mdl'};
  2133.     $dat->{'general_ieee'} = "MFG:" . $dat->{'general_mfg'} .
  2134.     ";MDL:" . $dat->{'general_mdl'} . ";" if 
  2135.     $dat->{'general_mfg'} && $dat->{'general_mdl'} &&
  2136.     !$dat->{'general_ieee'};
  2137.  
  2138.     # Generate the Foomatic printer ID
  2139.     $dat->{'id'} = generatepid($dat->{'make'}, $dat->{'model'})
  2140.     if !$dat->{'id'};
  2141.  
  2142.     # Find out printer's page description languages and suitable drivers
  2143.     if (!defined($parameters->{'drivers'})) {
  2144.     $parameters->{'drivers'} = [$dat->{'driver'}];
  2145.     }
  2146.     if (!defined($parameters->{'pdls'})) {
  2147.     $parameters->{'pdls'} = [split(',', $dat->{'general_cmd'})];
  2148.     } else {
  2149.     push(@{$parameters->{'pdls'}}, split(',', $dat->{'general_cmd'}));
  2150.     }
  2151.     apply_driver_and_pdl_info($dat, $parameters);
  2152.  
  2153.     # Find the maximum resolution
  2154.     if (defined($dat->{'args_byname'}{'Resolution'})) {
  2155.     my $maxres = 0;
  2156.     my $maxxres = 0;
  2157.     my $maxyres = 0;
  2158.     for my $reschoice (keys(%{$dat->{'args_byname'}{'Resolution'}{'vals_byname'}})) {
  2159.         my $r;
  2160.         my $x;
  2161.         my $y;
  2162.         if ($reschoice =~ /^(\d+)x(\d+)dpi$/i) {
  2163.         $x = $1;
  2164.         $y = $2;
  2165.         } elsif ($reschoice =~ /^(\d+)dpi$/i) {
  2166.         $x = $1;
  2167.         $y = $x;
  2168.         }
  2169.         $r = $x * $y;
  2170.         if ($r >= $maxres) {
  2171.         $maxres = $r;
  2172.         $maxxres = $x;
  2173.         $maxyres = $y
  2174.         }
  2175.     }
  2176.     if ($maxres == 0) {
  2177.         if (defined($dat->{'args_byname'}{'Resolution'}{'default'})) {
  2178.         my $res = $dat->{'args_byname'}{'Resolution'}{'default'};
  2179.         if ($res =~ /^(\d+)x(\d+)dpi$/i) {
  2180.             $dat->{'maxxres'} = $1;
  2181.             $dat->{'maxyres'} = $2;
  2182.         } elsif ($res =~ /^(\d+)dpi$/i) {
  2183.             $dat->{'maxxres'} = $1;
  2184.             $dat->{'maxyres'} = $dat->{'maxxres'};
  2185.         }
  2186.         }
  2187.     } else {
  2188.         $dat->{'maxxres'} = $maxxres;
  2189.         $dat->{'maxyres'} = $maxyres;
  2190.     }
  2191.     }
  2192.  
  2193.     if ($dat->{'maxpaperwidth'}) {
  2194.     my $wi = sprintf("%.1f", $dat->{'maxpaperwidth'} / 72);
  2195.     my $wc = sprintf("%.1f", $dat->{'maxpaperwidth'} / 72 * 2.54);
  2196.     my $wcomm = ($dat->{'maxpaperwidth'} < 280 ?
  2197.              "Label/Card printer" :
  2198.              ($dat->{'maxpaperwidth'} < 600 ?
  2199.               "Photo printer" :
  2200.               ($dat->{'maxpaperwidth'} < 800 ?
  2201.                "Standard format printer" :
  2202.                ($dat->{'maxpaperwidth'} < 1500 ?
  2203.             "Wide format printer" :
  2204.             "Large format printer"))));
  2205.     $dat->{'comment'} .=
  2206.         "      Maximum paper width: " . $wi . " inches / " . $wc .
  2207.         " cm\n" .
  2208.         "      (" . $wcomm . ")<p>\n\n" if $dat->{'maxpaperwidth'};
  2209.     }
  2210.     $dat->{'comment'} .=
  2211.     "      Printing engine speed: " . $dat->{'throughput'} .
  2212.     " pages/min<p>\n\n" if
  2213.     defined($dat->{'throughput'}) && ($dat->{'throughput'} > 1);
  2214.  
  2215.     # Set the defaults for the numerical options, taking into account
  2216.     # the "*FoomaticRIPDefault<option>: <value>" if they apply
  2217.     numericaldefaults($dat);
  2218.  
  2219.     # Some clean-up
  2220.     checklongnames($dat);
  2221.     generalentries($dat);
  2222.  
  2223.     return $dat;
  2224. }
  2225.  
  2226. sub generatepid {
  2227.     # Generate the Foomatic printer ID
  2228.     my ($mk, $md) = @_;
  2229.     $mk =~ s/\s+/_/g;
  2230.     $mk =~ s/\+/plus/g;
  2231.     $mk =~ s/[^A-Za-z0-9\._]/_/g;
  2232.     $mk =~ s/_+/_/g;
  2233.     $mk =~ s/^_//;
  2234.     $mk =~ s/_$//;
  2235.     $md =~ s/\s+/_/g;
  2236.     $md =~ s/\+/plus/g;
  2237.     $md =~ s/[^A-Za-z0-9\.\-]/_/g;
  2238.     $md =~ s/_+/_/g;
  2239.     $md =~ s/^_//;
  2240.     $md =~ s/_$//;
  2241.     return "$mk-$md";
  2242. }
  2243.  
  2244. sub perltoxml {
  2245.     my ($this, $mode) = @_;
  2246.  
  2247.     my $dat = $this->{'dat'};
  2248.     my $xml = "";
  2249.  
  2250.     $xml .= "<foomatic>\n" if !$mode || ($mode =~ /^c/i); 
  2251.  
  2252.     if (!$mode || ($mode =~ /^[cp]/i)) { 
  2253.     $xml .=
  2254.         "<printer id=\"printer/" . $dat->{'id'} . "\">\n" .
  2255.         "  <make>" . $dat->{'make'} . "</make>\n" .
  2256.         "  <model>" . $dat->{'model'} . "</model>\n" .
  2257.         "  <mechanism>\n" .
  2258.         ($dat->{'type'} ? "    <" . $dat->{'type'} . "/>\n" : ()) .
  2259.         ($dat->{'color'} ? "    <color/>\n" : ()) .
  2260.         ($dat->{'maxxres'} || $dat->{'maxyres'} ?
  2261.          "    <resolution>\n" .
  2262.          "      <dpi>\n" .
  2263.          ($dat->{'maxxres'} ?
  2264.           "        <x>" . $dat->{'maxxres'} . "</x>\n" : ()) .
  2265.          ($dat->{'maxyres'} ?
  2266.           "        <y>" . $dat->{'maxyres'} . "</y>\n" : ()) .
  2267.          "      </dpi>\n" .
  2268.          "    </resolution>\n" : ()) .
  2269.          "  </mechanism>\n";
  2270.     if (defined($dat->{'languages'}) ||
  2271.         defined($dat->{'pjl'}) ||
  2272.         defined($dat->{'ascii'})) {
  2273.         $xml .= "  <lang>\n";
  2274.         if (defined($dat->{'languages'})) {
  2275.         for  my $lang (@{$dat->{'languages'}}) {
  2276.             $xml .= "    <" . $lang->{'name'};
  2277.             if ($lang->{'level'}) {
  2278.             $xml .= " level=\"" . $lang->{'level'} . "\" ";
  2279.             }
  2280.             $xml .= "/>\n";
  2281.         }
  2282.         }
  2283.         if (defined($dat->{'pjl'})) {
  2284.         $xml .= "    <pjl />\n";
  2285.         }
  2286.         if (defined($dat->{'ascii'})) {
  2287.         $xml .= "    <text>\n";
  2288.         $xml .= "      <charset>us-ascii</charset>\n";
  2289.         $xml .= "    </text>\n";
  2290.         }
  2291.         $xml .= "  </lang>\n";
  2292.     }
  2293.     if (defined($dat->{'general_ieee'}) ||
  2294.         defined($dat->{'general_mfg'}) ||
  2295.         defined($dat->{'general_mdl'}) ||
  2296.         defined($dat->{'general_des'}) ||
  2297.         defined($dat->{'general_cmd'})) {
  2298.         $xml .= "  <autodetect>\n";
  2299.         $xml .= "    <general>\n";
  2300.         $xml .= "      <ieee1284>" . $dat->{'general_ieee'} .
  2301.         "</ieee1284>\n" if defined($dat->{'general_ieee'});
  2302.         $xml .= "      <manufacturer>" . $dat->{'general_mfg'} .
  2303.         "</manufacturer>\n" if defined($dat->{'general_mfg'});
  2304.         $xml .= "      <model>" . $dat->{'general_mdl'} .
  2305.         "</model>\n" if defined($dat->{'general_mdl'});
  2306.         $xml .= "      <description>" . $dat->{'general_des'} .
  2307.         "</description>\n" if defined($dat->{'general_des'});
  2308.         $xml .= "      <commandset>" . $dat->{'general_cmd'} .
  2309.         "</commandset>\n" if defined($dat->{'general_cmd'});
  2310.         $xml .= "    </general>\n";
  2311.         $xml .= "  </autodetect>\n";
  2312.     }
  2313.     $xml .= "  <functionality>" . $dat->{'functionality'} .
  2314.         "</functionality>\n" if defined($dat->{'functionality'});
  2315.     $xml .= "  <driver>" . $dat->{'driver'} .
  2316.         "</driver>\n" if defined($dat->{'driver'});
  2317.     if (defined($dat->{'drivers'})) {
  2318.         $xml .= "  <drivers>\n";
  2319.         for  my $drv (@{$dat->{'drivers'}}) {
  2320.         $xml .= "    <driver>\n";
  2321.         $xml .= "      <id>" . $drv->{'id'} . "</id>\n"
  2322.             if defined($drv->{'id'});
  2323.         $xml .= "      <ppd>" . $drv->{'ppd'} . "</ppd>\n"
  2324.             if defined($drv->{'ppd'});
  2325.         $xml .= "    </driver>\n";
  2326.         }
  2327.         $xml .= "  </drivers>\n";
  2328.     }
  2329.     $xml .= "  <unverified />\n" if $dat->{'unverified'};
  2330.     $xml .=
  2331.         "  <comments>\n" .
  2332.         "    <en>\n";
  2333.     $xml .= htmlify($dat->{'comment'}) . "\n" if $dat->{'comment'};
  2334.     $xml .=
  2335.         "    </en>\n" .
  2336.         "  </comments>\n" .
  2337.         "</printer>\n";
  2338.     }
  2339.  
  2340.     if (!$mode || ($mode =~ /^[cd]/i)) { 
  2341.     $xml .=
  2342.         "<driver id=\"driver/" . $dat->{'driver'} . "\">\n" .
  2343.         "  <name>" . $dat->{'driver'} . "</name>\n" .
  2344.         "  <execution>\n" .
  2345.         "    <filter />\n" .
  2346.         "    <prototype>" . $dat->{'cmd'} . "</prototype>\n" .
  2347.         $dat->{'cmd_pdf'} ? 
  2348.         "    <prototype_pdf>" . $dat->{'cmd_pdf'} . "</prototype_pdf>\n" :
  2349.         "" .
  2350.         "  </execution>\n" .
  2351.         "</driver>\n\n";
  2352.     }
  2353.  
  2354.     if (!$mode || ($mode =~ /^c/i)) { 
  2355.     $xml .= "<options>\n";
  2356.  
  2357.     foreach (@{$dat->{'args'}}) {
  2358.         my $type = $_->{'type'};
  2359.         my $optname = $_->{'name'};
  2360.         $xml .= "  <option type=\"$type\" " .
  2361.         "id=\"opt/" . $dat->{'driver'} . "-" . $optname . "\">\n";
  2362.         $xml .=
  2363.         "    <arg_longname>\n" .
  2364.         "      <en>" . $_->{'comment'} . "</en>\n" .
  2365.         "    </arg_longname>\n" .
  2366.         "    <arg_shortname>\n" .
  2367.         "      <en>" . $_->{'name'} . "</en>\n" .
  2368.         "    </arg_shortname>\n" .
  2369.         "    <arg_execution>\n";
  2370.         $xml .= "      <arg_group>" . $_->{'group'} . "</arg_group>\n"
  2371.         if $_->{'group'};
  2372.         $xml .= "      <arg_order>" . $_->{'order'} . "</arg_order>\n"
  2373.         if $_->{'order'};
  2374.         $xml .= "      <arg_spot>" . $_->{'spot'} . "</arg_spot>\n"
  2375.         if $_->{'spot'};
  2376.         $xml .= "      <arg_proto>" . $_->{'proto'} . "</arg_proto>\n"
  2377.         if $_->{'proto'};
  2378.         $xml .= "    </arg_execution>\n";
  2379.         
  2380.         if ($type eq 'enum') {
  2381.         $xml .= "    <enum_vals>\n";
  2382.         my $vals_byname = $_->{'vals_byname'};
  2383.         foreach (keys(%{$vals_byname})) {
  2384.             my $val = $vals_byname->{$_};
  2385.             $xml .=
  2386.             "      <enum_val id=\"ev/" . $dat->{'driver'} . "-" .
  2387.             $optname . "-" . $_ . "\">\n";
  2388.             $xml .=
  2389.             "        <ev_longname>\n" .
  2390.             "          <en>" . $val->{'comment'} . "</en>\n" .
  2391.             "        </ev_longname>\n" .
  2392.             "        <ev_shortname>\n" .
  2393.             "          <en>$_</en>\n" .
  2394.             "        </ev_shortname>\n";
  2395.  
  2396.             $xml .=
  2397.             "        <ev_driverval>" .
  2398.             $val->{'driverval'} .
  2399.             "</ev_driverval>\n" if $val->{'driverval'};
  2400.  
  2401.             $xml .= "      </enum_val>\n";
  2402.         }
  2403.         $xml .= "    </enum_vals>\n";
  2404.         }
  2405.  
  2406.         $xml .= "  </option>\n";
  2407.     }
  2408.  
  2409.     $xml .= "</options>\n";
  2410.     $xml .= "</foomatic>\n";
  2411.     }
  2412.     return $xml;
  2413. }
  2414.  
  2415. sub ppdgetdefaults {
  2416.  
  2417.     # Read a PPD and get only the defaults and the postpipe.
  2418.     my ($this, $ppdfile) = @_;
  2419.  
  2420.     # Open the PPD file
  2421.     open PPD, ($ppdfile !~ /\.gz$/i ? "< $ppdfile" : 
  2422.            "$sysdeps->{'gzip'} -cd \'$ppdfile\' |") or 
  2423.            die ("Unable to open PPD file \'$ppdfile\'\n");
  2424.  
  2425.     # We don't read the "COMDATA" lines of old Foomatic 2.0.x PPD files
  2426.     # here, because the defaults in the main PPD structure have priority.
  2427.     while(<PPD>) {
  2428.     # Foomatic should also work with PPD file downloaded under
  2429.     # Windows.
  2430.     $_ = undossify($_);
  2431.     # Parse keywords
  2432.     if (m!^\*FoomaticRIPPostPipe:\s*\"(.*)$!) {
  2433.         # "*FoomaticRIPPostPipe: <code>"
  2434.         my $line = $1;
  2435.         # Store the value
  2436.         # Code string can have multiple lines, read all of them
  2437.         my $cmd = "";
  2438.         while ($line !~ m!\"!) {
  2439.         if ($line =~ m!&&$!) {
  2440.             # line continues in next line
  2441.             $cmd .= substr($line, 0, -2);
  2442.         } else {
  2443.             # line ends here
  2444.             $cmd .= "$line\n";
  2445.         }
  2446.         # Read next line
  2447.         $line = <PPD>;
  2448.         chomp $line;
  2449.         }
  2450.         $line =~ m!^([^\"]*)\"!;
  2451.         $cmd .= $1;
  2452.         $this->{'dat'}{'postpipe'} = unhtmlify($cmd);
  2453.     } elsif (m!^\*Default([^/:\s]+):\s*([^/:\s]+)\s*$!) {
  2454.         # "*Default<option>: <value>"
  2455.         my $argname = $1;
  2456.         my $default = $2;
  2457.         if (defined($this->{'dat'}{'args_byname'}{$argname})) {
  2458.         # Store the value
  2459.         $this->{'dat'}{'args_byname'}{$argname}{'default'} =
  2460.             $default;
  2461.         }
  2462.     } elsif (m!^\*FoomaticRIPDefault([^/:\s]+):\s*([^/:\s]+)\s*$!) {
  2463.         # "*FoomaticRIPDefault<option>: <value>"
  2464.         # Used for numerical options only
  2465.         my $argname = $1;
  2466.         my $default = $2;
  2467.         if (defined($this->{'dat'}{'args_byname'}{$argname})) {
  2468.         # Store the value
  2469.         $this->{'dat'}{'args_byname'}{$argname}{'fdefault'} =
  2470.             $default;
  2471.         }
  2472.     }
  2473.     }
  2474.  
  2475.     close PPD;
  2476.  
  2477.     # Set the defaults for the numerical options, taking into account
  2478.     # the "*FoomaticRIPDefault<option>: <value>" if they apply
  2479.     #  similar to other places in the code
  2480.     numericaldefaults($this->{'dat'}); 
  2481.  
  2482. }
  2483.  
  2484. sub ppdvarsetdefaults {
  2485.  
  2486.     my ($this, @ppdlinesin) = @_;
  2487.  
  2488.     my @ppdlines;
  2489.     my $ppd;
  2490.  
  2491.     for (my $i = 0; $i < @ppdlinesin; $i ++) {
  2492.     my $line = $ppdlinesin[$i];
  2493.     # Remove a postpipe definition if one is there
  2494.     if ($line =~ m!^\*FoomaticRIPPostPipe:\s*\"(.*)$!) {
  2495.         # "*FoomaticRIPPostPipe: <code>"
  2496.         # Code string can have multiple lines, read all of them
  2497.         $line = $1;
  2498.         while ($line !~ m!\"!) {
  2499.         # Read next line
  2500.         $i++;
  2501.         $line = $ppdlinesin[$i];
  2502.         }
  2503.         # We also have to remove the "*End" line
  2504.         $i++;
  2505.         $line = $ppdlinesin[$i];
  2506.         if ($line !~ /^\*End/) {
  2507.         push(@ppdlines, $line);
  2508.         }
  2509.     } else {
  2510.         push(@ppdlines, $line);
  2511.     }
  2512.     }
  2513.     $ppd = join('', @ppdlines);
  2514.     # No option info read yet? Do not try to set deafaults
  2515.     return $ppd if !$this->{'dat'}{'args'};
  2516.  
  2517.     # If the settings for "PageSize" and "PageRegion" are different,
  2518.     # set the one for "PageRegion" to the one for "PageSize".
  2519.     if ($this->{'dat'}{'args_byname'}{'PageSize'}{'default'} ne
  2520.     $this->{'dat'}{'args_byname'}{'PageRegion'}{'default'}) {
  2521.     $this->{'dat'}{'args_byname'}{'PageRegion'}{'default'} =
  2522.         $this->{'dat'}{'args_byname'}{'PageSize'}{'default'}
  2523.     }
  2524.  
  2525.     # Numerical options: Set the "classical" default values
  2526.     # ("*Default<option>: <value>") to the value enumerated in the
  2527.     # list which is closest to the current default value.
  2528.     setnumericaldefaults($this->{'dat'}); 
  2529.  
  2530.     # Set the defaults in the PPD file to the current default
  2531.     # settings in the data structure
  2532.     for my $arg (@{$this->{'dat'}{'args'}}) {
  2533.     if (defined($arg->{'default'})) {
  2534.         my $name = $arg->{'name'};
  2535.         my $def = $arg->{'default'};
  2536.         if ($arg->{'type'} eq 'bool') {
  2537.         if ((lc($def) eq '1')   || (lc($def) eq 'on') || 
  2538.             (lc($def) eq 'yes') || (lc($def) eq 'true')) {
  2539.             $def='True';
  2540.         } elsif ((lc($def) eq '0')  || (lc($def) eq 'off') || 
  2541.              (lc($def) eq 'no') || (lc($def) eq 'false')) {
  2542.             $def='False';
  2543.         }
  2544.         $def = (checkoptionvalue($this->{'dat'}, $name, $def, 1) ?
  2545.             'True' : 'False');
  2546.         } elsif ($arg->{'type'} =~ /^(int|float)$/) {
  2547.         if (defined($arg->{'cdefault'})) {
  2548.             $def = $arg->{'cdefault'};
  2549.             undef $arg->{'cdefault'};
  2550.         }
  2551.         my $fdef = $arg->{'default'};
  2552.         $fdef = checkoptionvalue($this->{'dat'}, $name, $fdef, 1);
  2553.         $ppd =~ s!^(\*FoomaticRIPDefault$name:\s*)([^/:\s\r]*)(\s*\r?)$!$1$fdef$3!m;
  2554.         $def = checkoptionvalue($this->{'dat'}, $name, $def, 1);
  2555.         } elsif ($arg->{'type'} =~ /^(string|password)$/) {
  2556.         $def = checkoptionvalue($this->{'dat'}, $name, $def, 1);
  2557.         # An empty string cannot be an option name in a PPD file,
  2558.         # use "None" in this case, also substitute non-word characters
  2559.         # in the string to get a legal option name
  2560.         my $defcom = $def;
  2561.         my $defstr = $def;
  2562.         if ($def !~ /\S/) {
  2563.             $def = 'None';
  2564.             $defcom = '(None)';
  2565.             $defstr = '';
  2566.         } elsif ($def eq 'None') {
  2567.             $defcom = '(None)';
  2568.             $defstr = '';
  2569.         } else {
  2570.             $def =~ s/\W+/_/g;
  2571.             $def =~ s/^_+|_+$//g;
  2572.             $def = '_' if ($def eq '');
  2573.             $defcom =~ s/:/ /g;
  2574.             $defcom =~ s/^ +| +$//g;
  2575.         }
  2576.         # The default string is not available as an enumerated choice
  2577.         # ...
  2578.         if (($ppd !~ m!^\s*\*$arg->{name}\s+${def}[/:]!m) &&
  2579.             ($ppd !~ m!^\s*\*FoomaticRIPOptionSetting\s+$arg->{name}=${def}:!m)) {
  2580.             # ... build an appropriate PPD entry ...
  2581.             my $sprintfproto = $arg->{'proto'};
  2582.             $sprintfproto =~ s/\%(?!s)/\%\%/g;
  2583.             my $driverval = sprintf($sprintfproto, $defstr);
  2584.             my ($choicedef, $fchoicedef);
  2585.             if ($arg->{'style'} eq 'G') { # PostScript option
  2586.             $choicedef = sprintf("*%s %s/%s: \"%s\"", 
  2587.                          $name, $def, $defcom, $driverval);
  2588.             } else {
  2589.             my $header = sprintf
  2590.                 ("*FoomaticRIPOptionSetting %s=%s", $name, $def);
  2591.             $fchoicedef = ripdirective($header, $driverval); 
  2592.             if ($#{$arg->{'vals'}} >= 0) { # Visible non-PS option
  2593.                 $choicedef =
  2594.                 sprintf("*%s %s/%s: " .
  2595.                     "\"%%%% FoomaticRIPOptionSetting " .
  2596.                     "%s=%s\"", 
  2597.                     $name, $def, $defcom, $name, $def);
  2598.             }
  2599.             }
  2600.             if ($choicedef =~ /\n/s) {
  2601.             $choicedef .= "\n*End";
  2602.             }
  2603.             if ($fchoicedef =~ /\n/s) {
  2604.             $fchoicedef .= "\n*End";
  2605.             }
  2606.             if ($#{$arg->{'vals'}} == 0) {
  2607.             # ... and if there is only one choice, replace the one 
  2608.             # choice
  2609.             $ppd =~ s!^\*$name\s+.*?\".*?\"(\r?\n?\*End)?$!$choicedef!sm;
  2610.             $ppd =~ s!^\*FoomaticRIPOptionSetting\s+$name=.*?\".*?\"(\r?\n?\*End)?$!$fchoicedef!sm;
  2611.             } else {
  2612.             # ... and if there is no choice or more than one
  2613.             # choice, add a new choice for the default
  2614.             my $entrystr = 
  2615.                 ($choicedef ? "\n$choicedef" : "") .
  2616.                 ($fchoicedef ? "\n$fchoicedef" : "");
  2617.             for my $l ("Default$name:.*",
  2618.                    "OrderDependency.*$name",
  2619.                    "FoomaticRIPOptionMaxLength\\s+$name:.*",
  2620.                    "FoomaticRIPOptionPrototype\\s+$name:.*",
  2621.                    "FoomaticRIPOption\\s+$name:.*") {
  2622.                 $ppd =~ s!^(\*$l)$!$1$entrystr!m and last;
  2623.             }
  2624.             }
  2625.         }
  2626.         } else {
  2627.         $def = checkoptionvalue($this->{'dat'}, $name, $def, 0);
  2628.         }
  2629.         $ppd =~ s!^(\*Default$name:\s*)([^/:\s\r]*)(\s*\r?)$!$1$def$3!m
  2630.         if defined($def);
  2631.     }
  2632.     }
  2633.  
  2634.     # Update the postpipe
  2635.     if ($this->{'dat'}{'postpipe'}) {
  2636.     my $header = "*FoomaticRIPPostPipe";
  2637.     my $code = $this->{'dat'}{'postpipe'};
  2638.     my $postpipestr = ripdirective($header, $code) . "\n";
  2639.     if ($postpipestr =~ /\n.*\n/s) {
  2640.         $postpipestr .= "*End\n";
  2641.     }
  2642.     #$ppd =~ s/(\*PPD[^a-zA-Z0-9].*\n)/$1$postpipestr/s;
  2643.     $ppd =~ s/((\r\n|\n\r|\r|\n))/$1$postpipestr/s;
  2644.     }
  2645.     
  2646.     return $ppd;
  2647. }
  2648.  
  2649. sub ppdsetdefaults {
  2650.  
  2651.     my ($this, $ppdfile) = @_;
  2652.     
  2653.     # Load the complete PPD file into memory
  2654.     open PPD, ($ppdfile !~ /\.gz$/i ? "< $ppdfile" : 
  2655.            "$sysdeps->{'gzip'} -cd \'$ppdfile\' |") or
  2656.            die ("Unable to open PPD file \'$ppdfile\'\n");
  2657.     my @ppdlines = <PPD>;
  2658.     close PPD;
  2659.  
  2660.     # Set the defaults
  2661.     my $ppd = $this->ppdvarsetdefaults(@ppdlines);
  2662.     
  2663.     # Write back the modified PPD file
  2664.     open PPD, ($ppdfile !~ /\.gz$/i ? "> $ppdfile" : 
  2665.            "| $sysdeps->{'gzip'} > \'$ppdfile\'") or
  2666.     die ("Unable to open PPD file \'$ppdfile\' for writing\n");
  2667.     print PPD $ppd;
  2668.     close PPD;
  2669.     
  2670. }
  2671.  
  2672. # Some helper functions for reading the PPD file
  2673.  
  2674. sub unhtmlify {
  2675.     # Replace HTML/XML entities by the original characters
  2676.     my $str = $_[0];
  2677.     $str =~ s/\'/\'/g;
  2678.     $str =~ s/\"/\"/g;
  2679.     $str =~ s/\>/\>/g;
  2680.     $str =~ s/\</\</g;
  2681.     $str =~ s/\&/\&/g;
  2682.     return $str;
  2683. }
  2684.  
  2685. sub unhexify {
  2686.     # Replace hex notation for unprintable characters in PPD files
  2687.     # by the actual characters ex: "<0A>" --> chr(hex("0A"))
  2688.     my ($input, $encoding) = @_;
  2689.     my $output = "";
  2690.     my $hexmode = 0;
  2691.     my $hexstring = "";
  2692.     my $encoded = "";
  2693.     for (my $i = 0; $i < length($input); $i ++) {
  2694.     my $c = substr($input, $i, 1);
  2695.     if ($hexmode) {
  2696.         if ($c eq ">") {
  2697.         # End of hex string
  2698.         $encoded = '';
  2699.         for (my $i=0; $i < length($hexstring); $i+=2) {
  2700.             $encoded .= chr(hex(substr($hexstring, $i, 2)));
  2701.         }
  2702.         $output .= decode($encoding, $encoded);
  2703.         $hexmode = 0;
  2704.         } elsif ($c =~ /^[0-9a-fA-F]$/) {
  2705.         # Hexadecimal digit, two of them give a character
  2706.         $hexstring .= $c; 
  2707.         }
  2708.     } else {
  2709.         if ($c eq "<") {
  2710.         # Beginning of hex string
  2711.         $hexmode = 1;
  2712.         $hexstring = "";
  2713.         } else {
  2714.         # Normal character
  2715.         $output .= $c;
  2716.         }
  2717.     }
  2718.     }
  2719.     return $output;
  2720. }
  2721.  
  2722. sub undossify {
  2723.     # Remove "dossy" line ends ("\r\n") from a string
  2724.     my ($str) = @_;
  2725.     $str = "" if( !defined($str) );
  2726.     $str =~ s/\r\n/\n/gs;
  2727.     $str =~ s/\r$//s;
  2728.     return $str;
  2729. }
  2730.  
  2731. sub checkarg {
  2732.     # Check if there is already an argument record $argname in $dat, if not,
  2733.     # create one
  2734.     my ($dat, $argname) = @_;
  2735.     return if defined($dat->{'args_byname'}{$argname});
  2736.     # argument record
  2737.     my $rec;
  2738.     $rec->{'name'} = $argname;
  2739.     # Insert record in 'args' array for browsing all arguments
  2740.     push(@{$dat->{'args'}}, $rec);
  2741.     # 'args_byname' hash for looking up arguments by name
  2742.     $dat->{'args_byname'}{$argname} = $dat->{'args'}[$#{$dat->{'args'}}];
  2743.     # Default execution style is 'G' (PostScript) since all arguments for
  2744.     # which we don't find "*Foomatic..." keywords are usual PostScript
  2745.     # options
  2746.     $dat->{'args_byname'}{$argname}{'style'} = 'G';
  2747.     # Default prototype for code to insert, used by enum options
  2748.     $dat->{'args_byname'}{$argname}{'proto'} = '%s';
  2749.     # Mark option as hidden by default, as options consisting of only Foomatic
  2750.     # keywords are hidden. As soon as the PPD parser finds a non-Foomatic
  2751.     # keyword, it removes this mark
  2752.     $dat->{'args_byname'}{$argname}{'hidden'} = 1;
  2753. }
  2754.  
  2755. sub checksetting {
  2756.     # Check if there is already a choice record $setting in the $argname
  2757.     # argument in $dat, if not, create one
  2758.     my ($dat, $argname, $setting) = @_;
  2759.     return if 
  2760.     defined($dat->{'args_byname'}{$argname}{'vals_byname'}{$setting});
  2761.     # setting record
  2762.     my $rec;
  2763.     $rec->{'value'} = $setting;
  2764.     # Insert record in 'vals' array for browsing all settings
  2765.     push(@{$dat->{'args_byname'}{$argname}{'vals'}}, $rec);
  2766.     # 'vals_byname' hash for looking up settings by name
  2767.     $dat->{'args_byname'}{$argname}{'vals_byname'}{$setting} = 
  2768.     $dat->{'args_byname'}{$argname}{'vals'}[$#{$dat->{'args_byname'}{$argname}{'vals'}}];
  2769. }
  2770.  
  2771. sub removearg {
  2772.     # remove the argument record $argname from $dat
  2773.     my ($dat, $argname) = @_;
  2774.     return if !defined($dat->{'args_byname'}{$argname});
  2775.     # Remove 'args_byname' hash for looking up arguments by name
  2776.     delete $dat->{'args_byname'}{$argname};
  2777.     # Remove argument itself
  2778.     for (my $i = 0; $i <= $#{$dat->{'args'}}; $i ++) {
  2779.     if ($dat->{'args'}[$i]{'name'} eq $argname) {
  2780.         splice(@{$dat->{'args'}}, $i, 1);
  2781.         last;
  2782.     }
  2783.     }
  2784. }
  2785.  
  2786. sub booltoenum {
  2787.     # Turn the boolean argument $argname from $dat to an enumerated choice
  2788.     # equivalent to the original argument
  2789.     my ($dat, $argname) = @_;
  2790.     return if !defined($dat->{'args_byname'}{$argname});
  2791.     # Argument record
  2792.     my $arg = $dat->{'args_byname'}{$argname};
  2793.     # General settings
  2794.     $arg->{'type'} = 'enum';
  2795.     my $proto = $arg->{'proto'};
  2796.     $arg->{'proto'} = '%s';
  2797.     # Choice for 'true'
  2798.     if (!defined($arg->{'name_true'})) {
  2799.     $arg->{'name_true'} = $arg->{'name'};
  2800.     }
  2801.     checksetting($dat, $argname, 'true');
  2802.     my $truechoice = $arg->{'vals_byname'}{'true'};
  2803.     $truechoice->{'comment'} = longname($arg->{'name_true'});
  2804.     $truechoice->{'driverval'} = $proto;
  2805.     # Choice for 'false'
  2806.     if (!defined($arg->{'name_false'})) {
  2807.     $arg->{'name_false'} = "no$arg->{'name'}";
  2808.     }
  2809.     checksetting($dat, $argname, 'false');
  2810.     my $falsechoice = $arg->{'vals_byname'}{'false'};
  2811.     $falsechoice->{'comment'} = longname($arg->{'name_false'});
  2812.     $falsechoice->{'driverval'} = '';
  2813.     # Default value
  2814.     if ($arg->{'default'} eq '0') {
  2815.     $arg->{'default'} = 'false';
  2816.     } else {
  2817.     $arg->{'default'} = 'true';
  2818.     }
  2819. }
  2820.  
  2821. sub checkoptionvalue {
  2822.  
  2823.     ## This function checks whether a given value is valid for a given
  2824.     ## option. If yes, it returns a cleaned value (e. g. always 0 or 1
  2825.     ## for boolean options), otherwise "undef". If $forcevalue is set,
  2826.     ## we always determine a corrected value to insert (we never return
  2827.     ## "undef").
  2828.  
  2829.     # Is $value valid for the option named $argname?
  2830.     my ($dat, $argname, $value, $forcevalue) = @_;
  2831.  
  2832.     # Record for option $argname
  2833.     my $arg = $dat->{'args_byname'}{$argname};
  2834.  
  2835.     if ($arg->{'type'} eq 'bool') {
  2836.     if ((lc($value) eq 'true') ||
  2837.         (lc($value) eq 'on') ||
  2838.         (lc($value) eq 'yes') ||
  2839.         (lc($value) eq '1')) {
  2840.         return 1;
  2841.     } elsif ((lc($value) eq 'false') ||
  2842.          (lc($value) eq 'off') ||
  2843.          (lc($value) eq 'no') ||
  2844.          (lc($value) eq '0')) {
  2845.         return 0;
  2846.     } elsif ($forcevalue) {
  2847.         # This maps Unknown to mean False.  Good?  Bad?
  2848.         # It was done so in Foomatic 2.0.x, too.
  2849.         return 0;
  2850.     }
  2851.     } elsif ($arg->{'type'} eq 'enum') {
  2852.     if ($arg->{'vals_byname'}{$value}) {
  2853.         return $value;
  2854.     } elsif ((($arg->{'name'} eq "PageSize") ||
  2855.           ($arg->{'name'} eq "PageRegion")) &&
  2856.          (defined($arg->{'vals_byname'}{'Custom'})) &&
  2857.          ($value =~ m!^Custom\.([\d\.]+)x([\d\.]+)([A-Za-z]*)$!)) {
  2858.         # Custom paper size
  2859.         return $value;
  2860.     } elsif ($forcevalue) {
  2861.         # wtf!?  that's not a choice!
  2862.         # Return the first entry of the list
  2863.         my $firstentry = $arg->{'vals'}[0]{'value'};
  2864.         return $firstentry;
  2865.     }
  2866.     } elsif (($arg->{'type'} eq 'int') ||
  2867.          ($arg->{'type'} eq 'float')) {
  2868.     if (($value <= $arg->{'max'}) &&
  2869.         ($value >= $arg->{'min'})) {
  2870.         return $value;
  2871.     } elsif ($forcevalue) {
  2872.         my $newvalue;
  2873.         if ($value > $arg->{'max'}) {
  2874.         $newvalue = $arg->{'max'}
  2875.         } elsif ($value < $arg->{'min'}) {
  2876.         $newvalue = $arg->{'min'}
  2877.         }
  2878.         return $newvalue;
  2879.     }
  2880.     } elsif (($arg->{'type'} eq 'string') ||
  2881.          ($arg->{'type'} eq 'password')) {
  2882.     if (defined($arg->{'vals_byname'}{$value})) {
  2883.         return $value;
  2884.     } elsif (stringvalid($dat, $argname, $value)) {
  2885.         # Check whether the string is one of the enumerated choices
  2886.         my $sprintfproto = $arg->{'proto'};
  2887.         $sprintfproto =~ s/\%(?!s)/\%\%/g;
  2888.         my $driverval = sprintf($sprintfproto, $value);
  2889.         for my $val (@{$arg->{'vals'}}) {
  2890.         if (($val->{'driverval'} eq $driverval) ||
  2891.             ($val->{'driverval'} eq $value)) {
  2892.             return $val->{value};
  2893.         }
  2894.         }
  2895.         # No matching choice? Return the original string
  2896.         return $value;
  2897.     } elsif ($forcevalue) {
  2898.         my $str = substr($value, 0, $arg->{'maxlength'});
  2899.         if (stringvalid($dat, $argname, $str)) {
  2900.         return $str;
  2901.         } elsif ($#{$arg->{'vals'}} >= 0) {
  2902.         # First list item
  2903.         my $firstentry = $arg->{'vals'}[0]{'value'};
  2904.         return $firstentry;
  2905.         } else {
  2906.         # Empty string
  2907.         return 'None';
  2908.         }
  2909.     }
  2910.     }
  2911.     return undef;
  2912. }
  2913.  
  2914. sub stringvalid {
  2915.  
  2916.     ## Checks whether a user-supplied value for a string option is valid
  2917.     ## It must be within the length limit, should only contain allowed
  2918.     ## characters and match the given regexp
  2919.  
  2920.     # Option and string
  2921.     my ($dat, $argname, $value) = @_;
  2922.  
  2923.     my $arg = $dat->{'args_byname'}{$argname};
  2924.  
  2925.     # Maximum length
  2926.     return 0 if (defined($arg->{'maxlength'}) &&
  2927.          (length($value) > $arg->{'maxlength'}));
  2928.  
  2929.     # Allowed characters
  2930.     if ($arg->{'allowedchars'}) {
  2931.     my $chars = $arg->{'allowedchars'};
  2932.     $chars =~ s/(?<!\\)((\\\\)*)\//$2\\\//g;
  2933.     return 0 if $value !~ /^[$chars]*$/;
  2934.     }
  2935.  
  2936.     # Regular expression
  2937.     if ($arg->{'allowedregexp'}) {
  2938.     my $regexp = $arg->{'allowedregexp'};
  2939.     $regexp =~ s/(?<!\\)((\\\\)*)\//$2\\\//g;
  2940.     return 0 if $value !~ /$regexp/;
  2941.     }
  2942.  
  2943.     # All checks passed
  2944.     return 1;
  2945. }
  2946.  
  2947. sub checkoptions {
  2948.  
  2949.     ## Let the values of a boolean option being 0 or 1 instead of
  2950.     ## "True" or "False", range-check the defaults of all options and
  2951.     ## issue warnings if the values are not valid
  2952.  
  2953.     # Option set to be examined
  2954.     my ($dat, $optionset) = @_;
  2955.  
  2956.     for my $arg (@{$dat->{'args'}}) {
  2957.     if (defined($arg->{$optionset})) {
  2958.         $arg->{$optionset} =
  2959.         checkoptionvalue
  2960.         ($dat, $arg->{'name'}, $arg->{$optionset}, 1);
  2961.     }
  2962.     }
  2963.  
  2964.     # If the settings for "PageSize" and "PageRegion" are different,
  2965.     # set the one for "PageRegion" to the one for "PageSize".
  2966.     if ($dat->{'args_byname'}{'PageSize'}{$optionset} ne
  2967.     $dat->{'args_byname'}{'PageRegion'}{$optionset}) {
  2968.     $dat->{'args_byname'}{'PageRegion'}{$optionset} =
  2969.         $dat->{'args_byname'}{'PageSize'}{$optionset};
  2970.     }
  2971. }
  2972.  
  2973. # If the PageSize or PageRegion was changed, also change the other
  2974.  
  2975. sub syncpagesize {
  2976.     
  2977.     # Name and value of the option we set, and the option set where we
  2978.     # did the change
  2979.     my ($dat, $name, $value, $optionset) = @_;
  2980.  
  2981.     # Don't do anything if we were called with an option other than
  2982.     # "PageSize" or "PageRegion"
  2983.     return if (($name ne "PageSize") && ($name ne "PageRegion"));
  2984.     
  2985.     # Don't do anything if not both "PageSize" and "PageRegion" exist
  2986.     return if ((!defined($dat->{'args_byname'}{'PageSize'})) ||
  2987.            (!defined($dat->{'args_byname'}{'PageRegion'})));
  2988.     
  2989.     my $dest;
  2990.     
  2991.     # "PageSize" --> "PageRegion"
  2992.     if ($name eq "PageSize") {
  2993.     $dest = "PageRegion";
  2994.     }
  2995.     
  2996.     # "PageRegion" --> "PageSize"
  2997.     if ($name eq "PageRegion") {
  2998.     $dest = "PageSize";
  2999.     }
  3000.     
  3001.     # Do it!
  3002.     my $val;
  3003.     if ($val=valbyname($dat->{'args_byname'}{$dest}, $value)) {
  3004.     # Standard paper size
  3005.     $dat->{'args_byname'}{$dest}{$optionset} = $val->{'value'};
  3006.     } elsif ($val=valbyname($dat->{'args_byname'}{$dest}, "Custom")) {
  3007.     # Custom paper size
  3008.     $dat->{'args_byname'}{$dest}{$optionset} = $value;
  3009.     }
  3010. }
  3011.  
  3012. sub sortoptions {
  3013.  
  3014.     my ($dat, $only_options) = @_;
  3015.  
  3016.     # The following stuff is very awkward to implement in C, so we do
  3017.     # it here.
  3018.  
  3019.     # Sort options with "sortargs" function
  3020.     my @sortedarglist = sort sortargs @{$dat->{'args'}};
  3021.     @{$dat->{'args'}} = @sortedarglist;
  3022.  
  3023.     return if $only_options;
  3024.  
  3025.     # Sort values of enumerated options with "sortvals" function
  3026.     for my $arg (@{$dat->{'args'}}) {
  3027.     next if $arg->{'type'} !~ /^(enum|string|password)$/;
  3028.            my @sortedvalslist = sort sortvals keys(%{$arg->{'vals_byname'}});
  3029.     @{$arg->{'vals'}} = ();
  3030.     for my $i (@sortedvalslist) {
  3031.         my $val = $arg->{'vals_byname'}{$i};
  3032.         push (@{$arg->{'vals'}}, $val);
  3033.     }
  3034.     }
  3035.  
  3036. }
  3037.  
  3038. sub numericaldefaults {
  3039.  
  3040.     my ($dat) = @_;
  3041.  
  3042.     # Adobe's PPD specs do not support numerical
  3043.     # options. Therefore the numerical options are mapped to
  3044.     # enumerated options in the PPD file and their characteristics
  3045.     # as a numerical option are stored in "*Foomatic..."
  3046.     # keywords. Especially a default value between the enumerated
  3047.     # fixed values can be used as the default value. Then this
  3048.     # value must be given by a "*FoomaticRIPDefault<option>:
  3049.     # <value>" line in the PPD file. But this value is only valid,
  3050.     # if the "official" default given by a "*Default<option>:
  3051.     # <value>" line (it must be one of the enumerated values)
  3052.     # points to the enumerated value which is closest to this
  3053.     # value. This way a user can select a default value with a
  3054.     # tool only supporting PPD files but not Foomatic extensions.
  3055.     # This tool only modifies the "*Default<option>: <value>" line
  3056.     # and if the "*FoomaticRIPDefault<option>: <value>" had always
  3057.     # priority, the user's change in "*Default<option>: <value>"
  3058.     # had no effect.
  3059.  
  3060.     for my $arg (@{$dat->{'args'}}) {
  3061.     if ($arg->{'fdefault'}) {
  3062.         if ($arg->{'default'}) {
  3063.         if ($arg->{'type'} =~ /^(int|float)$/) {
  3064.             if ($arg->{'fdefault'} < $arg->{'min'}) {
  3065.             $arg->{'fdefault'} = $arg->{'min'};
  3066.             }
  3067.             if ($arg->{'fdefault'} > $arg->{'max'}) {
  3068.             $arg->{'fdefault'} = $arg->{'max'};
  3069.             }
  3070.             my $mindiff = abs($arg->{'max'} - $arg->{'min'});
  3071.             my $closestvalue;
  3072.             for my $val (@{$arg->{'vals'}}) {
  3073.             if (abs($arg->{'fdefault'} - $val->{'value'}) <
  3074.                 $mindiff) {
  3075.                 $mindiff = 
  3076.                 abs($arg->{'fdefault'} - $val->{'value'});
  3077.                 $closestvalue = $val->{'value'};
  3078.             }
  3079.             }
  3080.             if (($arg->{'default'} == $closestvalue) ||
  3081.             (abs($arg->{'default'} - $closestvalue) /
  3082.              $closestvalue < 0.001)) {
  3083.             $arg->{'default'} = $arg->{'fdefault'};
  3084.             }
  3085.         }
  3086.         } else {
  3087.         $arg->{'default'} = $arg->{'fdefault'};
  3088.         }
  3089.     }
  3090.     }
  3091. }
  3092.  
  3093. sub setnumericaldefaults {
  3094.  
  3095.     my ($dat) = @_;
  3096.  
  3097.     for my $arg (@{$dat->{'args'}}) {
  3098.     if ($arg->{'default'}) {
  3099.         if ($arg->{'type'} =~ /^(int|float)$/) {
  3100.         if ($arg->{'default'} < $arg->{'min'}) {
  3101.             $arg->{'default'} = $arg->{'min'};
  3102.             $arg->{'cdefault'} = $arg->{'default'};
  3103.         } elsif ($arg->{'default'} > $arg->{'max'}) {
  3104.             $arg->{'default'} = $arg->{'max'};
  3105.             $arg->{'cdefault'} = $arg->{'default'};
  3106.         } elsif (defined($arg->{'vals_byname'}{$arg->{'default'}})) {
  3107.             $arg->{'cdefault'} = $arg->{'default'};
  3108.         } else {
  3109.             my $mindiff = abs($arg->{'max'} - $arg->{'min'});
  3110.             my $closestvalue;
  3111.             for my $val (@{$arg->{'vals'}}) {
  3112.             if (abs($arg->{'default'} - $val->{'value'}) <
  3113.                 $mindiff) {
  3114.                 $mindiff = 
  3115.                 abs($arg->{'default'} - $val->{'value'});
  3116.                 $closestvalue = $val->{'value'};
  3117.             }
  3118.             }
  3119.             $arg->{'cdefault'} = $closestvalue;
  3120.         }
  3121.         }
  3122.     }
  3123.     }
  3124.  
  3125. }
  3126.  
  3127. sub generalentries {
  3128.  
  3129.     my ($dat) = @_;
  3130.  
  3131.     $dat->{'compiled-at'} = localtime(time());
  3132.     $dat->{'timestamp'} = time();
  3133.  
  3134.     my $user = `whoami`; chomp $user;
  3135.     my $host = `hostname`; chomp $host;
  3136.  
  3137.     $dat->{'compiled-by'} = "$user\@$host";
  3138.  
  3139. }
  3140.  
  3141. sub checklongnames {
  3142.  
  3143.     my ($dat) = @_;
  3144.  
  3145.     # Add missing longnames/translations
  3146.     for my $arg (@{$dat->{'args'}}) {
  3147.     if (!($arg->{'comment'})) {
  3148.         $arg->{'comment'} = longname($arg->{'name'});
  3149.     }
  3150.     for my $i (@{$arg->{'vals'}}) {
  3151.         if (!($i->{'comment'})) {
  3152.         $i->{'comment'} = longname($i->{'value'});
  3153.         }
  3154.     }
  3155.     }
  3156. }
  3157.  
  3158. sub cutguiname {
  3159.     
  3160.     # If $shortgui is set and $str is longer than 39 characters, return the
  3161.     # first 39 characters of $str, otherwise the complete $str. 
  3162.  
  3163.     my ($str, $shortgui) = @_;
  3164.  
  3165.     if (($shortgui) && (length($str) > 39)) {
  3166.     return substr($str, 0, 39);
  3167.     } else {
  3168.     return $str;
  3169.     }
  3170. }
  3171.  
  3172. sub deviceIDfromDBEntry {
  3173.  
  3174.     my ($dat) = @_;
  3175.  
  3176.     # Complete IEEE 1284 ID string?
  3177.     my $ieee1284;
  3178.     $ieee1284 = $dat->{'general_ieee'} or $ieee1284 = $dat->{'pnp_ieee'} or
  3179.     $ieee1284 = $dat->{'par_ieee'} or $ieee1284 = $dat->{'usb_ieee'} or 
  3180.     $ieee1284 = $dat->{'snmp_ieee'} or $ieee1284 = "";
  3181.     # Extract data fields from the ID string
  3182.     my $ieeemake;
  3183.     my $ieeemodel;
  3184.     my $ieeecmd;
  3185.     my $ieeedes;
  3186.     if ($ieee1284) {
  3187.     $ieee1284 =~ /(MFG|MANUFACTURER):\s*([^:;]+);?/i;
  3188.     $ieeemake = $2;
  3189.     $ieee1284 =~ /(MDL|MODEL):\s*([^:;]+);?/i;
  3190.     $ieeemodel = $2;
  3191.     $ieee1284 =~ /(CMD|COMMANDS?\s*SET):\s*([^:;]+);?/i;
  3192.     $ieeecmd = $2;
  3193.     $ieee1284 =~ /(DES|DESCRIPTION):\s*([^:;]+);?/i;
  3194.     $ieeedes = $2;
  3195.     }
  3196.     # Auto-detection data listed field by field in the printer XML file?
  3197.     my $pnpmake;
  3198.     $pnpmake = $ieeemake or $pnpmake = $dat->{'general_mfg'} or 
  3199.     $pnpmake = $dat->{'pnp_mfg'} or $pnpmake = $dat->{'par_mfg'} or
  3200.     $pnpmake = $dat->{'usb_mfg'} or $pnpmake = "";
  3201.     my $pnpmodel;
  3202.     $pnpmodel = $ieeemodel or $pnpmodel = $dat->{'general_mdl'} or
  3203.     $pnpmodel = $dat->{'pnp_mdl'} or $pnpmodel = $dat->{'par_mdl'} or
  3204.     $pnpmodel = $dat->{'usb_mdl'} or $pnpmodel = "";
  3205.     my $pnpcmd;
  3206.     $pnpcmd = $ieeecmd or $pnpcmd = $dat->{'general_cmd'} or 
  3207.     $pnpcmd = $dat->{'pnp_cmd'} or $pnpcmd = $dat->{'par_cmd'} or
  3208.     $pnpcmd = $dat->{'usb_cmd'} or $pnpcmd = "";
  3209.     my $pnpdescription;
  3210.     $pnpdescription = $ieeedes or
  3211.     $pnpdescription = $dat->{'general_des'} or
  3212.     $pnpdescription = $dat->{'pnp_des'} or 
  3213.     $pnpdescription = $dat->{'par_des'} or
  3214.     $pnpdescription = $dat->{'usb_des'} or
  3215.     $pnpdescription = "";
  3216.     if ((!$ieee1284) && ((($pnpmake) && ($pnpmodel)) || ($pnpdescription))){
  3217.     $ieee1284 .= "MFG:$pnpmake;" if $pnpmake;
  3218.     $ieee1284 .= "MDL:$pnpmodel;" if $pnpmodel;
  3219.     $ieee1284 .= "CMD:$pnpcmd;" if $pnpcmd;
  3220.     $ieee1284 .= "DES:$pnpdescription;" if $pnpdescription;
  3221.     }
  3222.     return $ieee1284;
  3223. }
  3224.  
  3225. sub ppd1284DeviceID {
  3226.  
  3227.     # Clean up IEEE-1284 device ID to only contain the fields relevant
  3228.     # to printer model auto-detection (MFG, MDL, DES, CMD, SKU, DRV), thus
  3229.     # the line length limit of PPDs does not get exceeded on very long
  3230.     # ID strings.
  3231.  
  3232.     my ($id) = @_;
  3233.     my $ppdid = "";
  3234.     
  3235.     foreach my $field ("(MFG|MANUFACTURER)", "(MDL|MODEL)", "(CMD|COMMANDS?\\s*SET)", "(DES|DESCRIPTION)", "SKU", "DRV") {
  3236.     if ($id =~ m/(\b$field:\s*[^:;]+;?)/is) {
  3237.         my $f = $1;
  3238.         $ppdid .= ";" if $ppdid && $ppdid !~ /;$/;
  3239.         $ppdid .= $f;
  3240.     }
  3241.     }
  3242.  
  3243.     return $ppdid;
  3244. }
  3245.  
  3246. sub getppdheaderdata {
  3247.     
  3248.     my ($dat, $driver, $recdriver) = @_;
  3249.  
  3250.     my $ieee1284 = deviceIDfromDBEntry($dat);
  3251.  
  3252.     # Add driver profile to device ID string, so we get it into the
  3253.     # PPD listing output of CUPS
  3254.     my @profileitems = ();
  3255.     my $profileelements =
  3256.     [["manufacturersupplied", "M"],
  3257.      ["obsolete", "O"],
  3258.      ["free", "F"],
  3259.      ["patents", "P"],
  3260.      ["supportcontacts", "S"],
  3261.      ["type", "T"],
  3262.      ["drvmaxresx", "X"],
  3263.      ["drvmaxresy", "Y"],
  3264.      ["drvcolor", "C"],
  3265.      ["text", "t"],
  3266.      ["lineart", "l"],
  3267.      ["graphics", "g"],
  3268.      ["photo", "p"],
  3269.      ["load", "d"], 
  3270.      ["speed", "s"]];
  3271.     my $drvfield = '';
  3272.     foreach my $item (@{$profileelements}) {
  3273.     my ($perlkey, $devidkey) = @{$item};
  3274.     if ($perlkey eq "manufacturersupplied") {
  3275.         my $ms;
  3276.         if (defined($dat->{$perlkey})) {
  3277.         $ms = $dat->{$perlkey};
  3278.         } elsif (defined($dat->{'driverproperties'}{$driver}{$perlkey})) {
  3279.         $ms = $dat->{'driverproperties'}{$driver}{$perlkey};
  3280.         }
  3281.         $drvfield .= "," . $devidkey .
  3282.         ($ms eq "1" ? "1" : ($dat->{make} =~ m,^($ms)$,i ? "1" : "0"));
  3283.     } elsif ($perlkey eq "supportcontacts") {
  3284.         my $sc;
  3285.         if (defined($dat->{$perlkey})) {
  3286.         $sc = $dat->{$perlkey};
  3287.         } elsif (defined($dat->{'driverproperties'}{$driver}{$perlkey})) {
  3288.         $sc = $dat->{'driverproperties'}{$driver}{$perlkey};
  3289.         }
  3290.         if ($sc) {
  3291.         my $commercial = 0;
  3292.         my $voluntary = 0;
  3293.         my $unknown = 0;
  3294.         foreach my $entry (@{$sc}) {
  3295.             if ($entry->{'level'} =~ /^commercial$/i) {
  3296.             $commercial = 1;
  3297.             } elsif ($entry->{'level'} =~ /^voluntary$/i) {
  3298.             $voluntary = 1;
  3299.             } else {
  3300.             $unknown = 1;
  3301.             }
  3302.         }
  3303.         $drvfield .= "," . $devidkey . ($commercial ? "c" : "") .
  3304.             ($voluntary ? "v" : "") . ($unknown ? "u" : "");
  3305.         }
  3306.     } else {
  3307.         if (defined($dat->{$perlkey})) {
  3308.         $drvfield .= "," . $devidkey . $dat->{$perlkey};
  3309.         } elsif (defined($dat->{'driverproperties'}{$driver}{$perlkey})) {
  3310.         $drvfield .= "," . $devidkey . 
  3311.             $dat->{'driverproperties'}{$driver}{$perlkey};
  3312.         }
  3313.     }
  3314.     }
  3315.     $ieee1284 .= ";" if $ieee1284 && $ieee1284 !~ /;$/;
  3316.     $ieee1284 .= "DRV:D$driver" .
  3317.     ($recdriver ? ($driver eq $recdriver ? ",R1" : ",R0") : "") .
  3318.     "$drvfield;";
  3319.  
  3320.     # Remove everything from the device ID which is not relevant to
  3321.     # auto-detection of the printer model.
  3322.     $ieee1284 = ppd1284DeviceID($ieee1284) if $ieee1284;
  3323.  
  3324.     my $make = $dat->{'make'};
  3325.     my $model = $dat->{'model'};
  3326.  
  3327.     $ieee1284 =~ /(MFG|MANUFACTURER):\s*([^:;]+);?/i;
  3328.     my $pnpmake = $2;
  3329.     $pnpmake = $make if !$pnpmake;
  3330.     $ieee1284 =~ /(MDL|MODEL):\s*([^:;]+);?/i;
  3331.     my $pnpmodel = $2;
  3332.     $pnpmodel = $model if (!$pnpmodel) || ($pnpmodel eq $pnpmake);
  3333.  
  3334.     # File name for the PPD file
  3335.     my $filename = join('-',($dat->{'make'},
  3336.                  $dat->{'model'},
  3337.                  $driver));;
  3338.     $filename =~ s![ /\(\)\,]!_!g;
  3339.     $filename =~ s![\+]!plus!g;
  3340.     $filename =~ s!__+!_!g;
  3341.     $filename =~ s!_$!!;
  3342.     $filename =~ s!^_!!;
  3343.     $filename =~ s!_-!-!;
  3344.     $filename =~ s!-_!-!;
  3345.     my $longname = "$filename.ppd";
  3346.  
  3347.     # Driver name
  3348.     my $drivername = $driver;
  3349.  
  3350.     # Do we use the recommended driver?
  3351.     my $driverrecommended = "";
  3352.     if ($driver eq $recdriver) {
  3353.     $driverrecommended = " (recommended)";
  3354.     }
  3355.     
  3356.     # evil special case.
  3357.     $drivername = "stp-4.0" if $drivername eq 'stp';
  3358.  
  3359.     # Nickname for the PPD file
  3360.     my $nickname =
  3361.     "$make $model Foomatic/$drivername$driverrecommended";
  3362.     my $modelname = "$make $model";
  3363.     # Remove forbidden characters (Adobe PPD spec 4.3 section 5.3)
  3364.     $modelname =~ s/[^A-Za-z0-9 \.\/\-\+]//gs;
  3365.  
  3366.     return ($ieee1284,$pnpmake,$pnpmodel,$filename,$longname,
  3367.         $drivername,$nickname,$modelname);
  3368. }
  3369.  
  3370. #
  3371. # PPD generation
  3372. #
  3373.  
  3374. # member( $a, @b ) returns 1 if $a is in @b, 0 otherwise.
  3375. sub member { my $e = shift; foreach (@_) { $e eq $_ and return 1 } 0 };
  3376.  
  3377.  
  3378. sub setgroupandorder {
  3379.  
  3380.     # Set group of member options. Make also sure that the composite
  3381.     # option will be inserted into the PostScript code before all its
  3382.     # # members are inserted (by means of the section and the order #
  3383.     # number).
  3384.  
  3385.     # The composite option to be treated ($arg)
  3386.     my ($db, $arg, $members_in_subgroup) = @_;
  3387.     
  3388.     # The Perl data structure of the current printer/driver combo.
  3389.     my $dat = $db->{'dat'};
  3390.  
  3391.     # Here we are only interested in composite options, skip the others
  3392.     return if $arg->{'style'} ne 'X';
  3393.  
  3394.     my $name = $arg->{'name'};
  3395.     my $group = $arg->{'group'};
  3396.     my $order = $arg->{'order'};
  3397.     my $section = $arg->{'section'};
  3398.     my @members = @{$arg->{'members'}};
  3399.  
  3400.     for my $m (@members) {
  3401.     my $a = $dat->{'args_byname'}{$m};
  3402.  
  3403.     # If $members_in_subgroup is set, the group should be a
  3404.     # subgroup of the group where the composite option is
  3405.     # located, named as the composite option. Otherwise the
  3406.     # group will get a new main group.
  3407.     if (($members_in_subgroup) && ($group)) {
  3408.         $a->{'group'} = "$group/$name";
  3409.     } else {
  3410.         $a->{'group'} = "$name";
  3411.     }
  3412.  
  3413.     # If the member is composite, call this function on it
  3414.     # recursively.  This sets the groups of the members of this
  3415.     # composite member option and also sets the section and order
  3416.     # number of this composite member, so that we can set section
  3417.     # and order of the currently treated option
  3418.     $db->setgroupandorder($a, $members_in_subgroup)
  3419.         if $a->{'style'} eq 'X';
  3420.  
  3421.     # Determine section and order number for the composite option
  3422.     # Order of the DSC sections of a PostScript file
  3423.     my @sectionorder = ("JCLSetup", "Prolog", "DocumentSetup", 
  3424.                 "AnySetup", "PageSetup");
  3425.  
  3426.     # Set default for missing section value in member
  3427.     if (!defined($a->{'section'})) {$a->{'section'} = "AnySetup";}
  3428.     my $minsection;
  3429.     for my $s (@sectionorder) {
  3430.         if (($s eq $arg->{'section'}) || ($s eq $a->{'section'})) {
  3431.         $minsection = $s;
  3432.         last;
  3433.         }
  3434.     }
  3435.  
  3436.     # If the current member option is in an earlier section,
  3437.     # put also the composite option into it. Do never put the
  3438.     # composite option into the JCL setup because in the JCL
  3439.     # header PostScript comments are not allowed.
  3440.     $arg->{'section'} = ($minsection ne "JCLSetup" ?
  3441.                  $minsection : "Prolog");
  3442.  
  3443.     # Let the order number of the composite option be less
  3444.     # than the order number of the current member
  3445.     if ($arg->{'order'} >= $a->{'order'}) {
  3446.         $arg->{'order'} = $a->{'order'} - 1;
  3447.         if ($arg->{'order'} < 0) {
  3448.         $arg->{'order'} = 0;
  3449.         }
  3450.     }
  3451.     }
  3452. }
  3453.  
  3454.  
  3455. # Return a generic Adobe-compliant PPD for the "foomatic-rip" filter script
  3456. # for all spoolers.  Built from the standard data; you must call getdat()
  3457. # first.
  3458. sub getppd (  $ $ $ ) {
  3459.  
  3460.     # If $shortgui is set, all GUI strings ("translations" in PPD
  3461.     # files) will be cut to a maximum length of 39 characters. This is
  3462.     # needed by the current (as of July 2003) version of the CUPS
  3463.     # PostScript driver for Windows.
  3464.  
  3465.     # If $members_in_subgroup is set, the member options of a composite
  3466.     # option go into a subgroup of the group where the composite option
  3467.     # is located. Otherwise the member options go into a new main group
  3468.  
  3469.     my ($db, $shortgui, $members_in_subgroup) = @_;
  3470.  
  3471.     die "you need to call getdat first!\n" 
  3472.     if (!defined($db->{'dat'}));
  3473.  
  3474.     # The Perl data structure of the current printer/driver combo.
  3475.     my $dat = $db->{'dat'};
  3476.  
  3477.     # Do we have a custom pre-made PPD? If so, return this one
  3478.     if (defined($dat->{'ppdfile'})) {
  3479.     my $ppdfile = $dat->{'ppdfile'};
  3480.     $ppdfile = "${ppdfile}.gz" if (! -r $ppdfile);
  3481.     if (-r $ppdfile) {
  3482.         # Load the complete PPD file into memory
  3483.         if (open PPD, ($ppdfile !~ /\.gz$/i ? "< $ppdfile" : 
  3484.                "$sysdeps->{'gzip'} -cd \'$ppdfile\' |")) {
  3485.         my @ppdlines = <PPD>;
  3486.         close PPD;
  3487.         # Set the default values
  3488.         my $ppd = $db->ppdvarsetdefaults(@ppdlines);
  3489.         return $ppd;
  3490.         }
  3491.     }
  3492.     }
  3493.  
  3494.     my @optionblob; # Lines for command line and options in the PPD file
  3495.  
  3496.     # Insert the printer/driver IDs and the command line prototype
  3497.     # right before the option descriptions
  3498.  
  3499.     push(@optionblob, "*FoomaticIDs: $dat->{'id'} $dat->{'driver'}\n");
  3500.     my $header = "*FoomaticRIPCommandLine";
  3501.     my $cmdline = $dat->{'cmd'};
  3502.     my $cmdlinestr = ripdirective($header, $cmdline);
  3503.     if ($cmdline) {
  3504.     # Insert the "*FoomaticRIPCommandLine" directive, but only if
  3505.     # the command line prototype is not empty
  3506.     push(@optionblob, "$cmdlinestr\n");
  3507.     if ($cmdlinestr =~ /\n/s) {
  3508.         push(@optionblob, "*End\n");
  3509.     }
  3510.     }
  3511.     $header = "*FoomaticRIPCommandLinePDF";
  3512.     $cmdline = $dat->{'cmd_pdf'};
  3513.     $cmdlinestr = ripdirective($header, $cmdline);
  3514.     if ($cmdline) {
  3515.     # Insert the "*FoomaticRIPCommandLine" directive, but only if
  3516.     # the command line prototype is not empty
  3517.     push(@optionblob, "$cmdlinestr\n");
  3518.     if ($cmdlinestr =~ /\n/s) {
  3519.         push(@optionblob, "*End\n");
  3520.     }
  3521.     }
  3522.     if ($dat->{'drivernopageaccounting'}) {
  3523.     push(@optionblob, "*FoomaticRIPNoPageAccounting: True\n");
  3524.     }
  3525.  
  3526.     # Search for composite options and prepare the member options
  3527.     # of the found composite options
  3528.     for my $arg (@{$dat->{'args'}}) {
  3529.     # Here we are only interested in composite options, skip the others
  3530.     next if $arg->{'style'} ne 'X';
  3531.     my $name = $arg->{'name'};
  3532.     my $com  = $arg->{'comment'};
  3533.     my $group = $arg->{'group'};
  3534.     my $order = $arg->{'order'};
  3535.     my $section = $arg->{'section'};
  3536.  
  3537.     # The "PageRegion" option is generated automatically, so ignore an
  3538.     # already existing "PageRegion". 
  3539.     next if $name eq "PageRegion";
  3540.  
  3541.     # Set default for missing section value
  3542.     if (!defined($section)) {$arg->{'section'} = "AnySetup";}
  3543.  
  3544.     # Set default for missing tranaslation/longname
  3545.     if (!$com) {$com = longname($name);}
  3546.  
  3547.     my @members;
  3548.  
  3549.     # Go through all choices of the composite option to find its
  3550.     # member options
  3551.     for my $v (@{$arg->{'vals'}}) {
  3552.         my @settings = split(/\s+/s, $v->{'driverval'});
  3553.         for my $s (@settings) {
  3554.         if (($s =~ /^([^=]+)=/) ||
  3555.             ($s =~ /^[Nn][Oo]([^=]+)$/) ||
  3556.             ($s =~ /^([^=]+)$/)) {
  3557.             my $m = $1;
  3558.             # Does the found member exist for this printer/driver
  3559.             # combo?
  3560.             if (defined($dat->{'args_byname'}{$m})) {
  3561.             # Add it to the list of found member options
  3562.             if (!member($m, @members)) {
  3563.                 push(@members, $1);
  3564.             }
  3565.             # Clean up entries for boolean options
  3566.             if ($s !~ /=/) {
  3567.                 if ($s =~ /^[Nn][Oo]$m$/) {
  3568.                 $v->{'driverval'} =~
  3569.                     s/(^|\s)$s($|\s)/$1$m=false$2/;
  3570.                 } else {
  3571.                 $v->{'driverval'} =~ 
  3572.                     s/(^|\s)$s($|\s)/$1$m=true$2/;
  3573.                 }
  3574.             }
  3575.             } else {
  3576.             # Remove it from the choice of the composite
  3577.             # option
  3578.             $v->{'driverval'} =~ s/$s\s*//;
  3579.             $v->{'driverval'} =~ s/\s*$//;
  3580.             }
  3581.         }
  3582.         }
  3583.     }
  3584.  
  3585.     # Add the member list to the data structure of the composite
  3586.     # option. We need it for the recursive setting of group names
  3587.     # and order numbers
  3588.     $arg->{'members'} = \@members;
  3589.  
  3590.     # Add a "From<Composite>" choice which will be the
  3591.     # default. Check also all members if they are hidden, if so,
  3592.     # this composite option is a forced composite option.
  3593.     my $nothiddenmemberfound = 0;
  3594.     for my $m (@members) {
  3595.         my $a = $dat->{'args_byname'}{$m};
  3596.  
  3597.         # Mark this member as being a member of the current
  3598.         # composite option
  3599.         $a->{'memberof'} = $name;
  3600.  
  3601.         # Convert boolean options to enumerated choice options, so
  3602.         # that we can add the "From<Composite>" choice.
  3603.         if ($a->{'type'} eq 'bool') {
  3604.         booltoenum($dat, $a->{'name'});
  3605.         }
  3606.  
  3607.         # Is this member option hidden?
  3608.         if (!$a->{'hidden'}) {
  3609.         $nothiddenmemberfound = 1;
  3610.         }
  3611.  
  3612.         # In case of a forced composite option mark the member option
  3613.         # as hidden.
  3614.         if (defined($arg->{'substyle'}) &&
  3615.         ($arg->{'substyle'} eq 'F')) {
  3616.         $a->{'hidden'} = 1;
  3617.         }
  3618.  
  3619.         # Do not add a "From<Composite>" choice to an option with only
  3620.         # one choice
  3621.         next if $#{$a->{'vals'}} < 1;
  3622.  
  3623.         if (!defined($a->{'vals_byname'}{"From$name"})) {
  3624.         # Add "From<Composite>" choice
  3625.         # setting record
  3626.         my $rec;
  3627.         $rec->{'value'} = "From$name";
  3628.         $rec->{'comment'} = "Controlled by '$com'";
  3629.         # We mark the driverval as invalid with a non-printable
  3630.         # character, this means that the code to insert will be an
  3631.         # empty string in the PPD.
  3632.         $rec->{'driverval'} = "\x01";
  3633.         # Insert record as the first item in the 'vals' array
  3634.         unshift(@{$a->{'vals'}}, $rec);
  3635.         # Update 'vals_byname' hash
  3636.         $a->{'vals_byname'}{$rec->{'value'}} = $a->{'vals'}[0];
  3637.         for (my $i = 1; $i <= $#{$a->{'vals'}}; $i ++) {
  3638.             $a->{'vals_byname'}{$a->{'vals'}[$i]{'value'}} =
  3639.             $a->{'vals'}[$i];
  3640.         }
  3641.         } else {
  3642.         # Only update the values
  3643.         $a->{'vals_byname'}{"From$name"}{'value'} = "From$name";
  3644.         $a->{'vals_byname'}{"From$name"}{'comment'} =
  3645.             "Controlled by '$com'";
  3646.         $a->{'vals_byname'}{"From$name"}{'driverval'} = "\x01";
  3647.         }
  3648.  
  3649.         # Set default to the new "From<Composite>" choice
  3650.         $a->{'default'} = "From$name";
  3651.     }
  3652.  
  3653.     # If all member options are hidden, this composite option is
  3654.     # a forced composite option and has to be marked appropriately
  3655.     if (!$nothiddenmemberfound) {
  3656.         $arg->{'substyle'} = 'F';
  3657.     }
  3658.     }
  3659.  
  3660.     # Now recursively set the groups and the order sections and numbers
  3661.     # for all composite options and their members.
  3662.     for my $arg (@{$dat->{'args'}}) {
  3663.     # The recursion should only be started in composite options
  3664.     # which are not member of another composite option.
  3665.     $db->setgroupandorder($arg, $members_in_subgroup) 
  3666.         if ($arg->{'style'} eq 'X') and (!$arg->{'memberof'});
  3667.     }
  3668.  
  3669.     # Sort options with "sortargs" function after they were re-grouped
  3670.     # due to the composite options
  3671.     my @sortedarglist = sort sortargs @{$dat->{'args'}};
  3672.     @{$dat->{'args'}} = @sortedarglist;
  3673.  
  3674.     # Construct the option entries for the PPD file
  3675.  
  3676.     my @groupstack; # In which group are we currently
  3677.  
  3678.     for my $arg (@{$dat->{'args'}}) {
  3679.     my $name = $arg->{'name'};
  3680.     my $type = $arg->{'type'};
  3681.     my $com  = $arg->{'comment'};
  3682.     my $default = $arg->{'default'};
  3683.     my $order = $arg->{'order'};
  3684.     my $spot = $arg->{'spot'};
  3685.     my $section = $arg->{'section'};
  3686.     my $cmd = $arg->{'proto'};
  3687.     my @group;
  3688.     @group = split("/", $arg->{'group'}) if defined($arg->{'group'});
  3689.     my $idx = $arg->{'idx'};
  3690.  
  3691.     # What is the execution style of the current option? Skip options
  3692.         # of unknown execution style
  3693.     my $optstyle = ($arg->{'style'} eq 'G' ? "PS" :
  3694.             ($arg->{'style'} eq 'J' ? "JCL" :
  3695.              ($arg->{'style'} eq 'C' ? "CmdLine" :
  3696.               ($arg->{'style'} eq 'X' ? "Composite" :
  3697.                "Unknown"))));
  3698.     next if $optstyle eq "Unknown";
  3699.  
  3700.     # The "PageRegion" option is generated automatically, so ignore an
  3701.     # already existing "PageRegion". 
  3702.     next if $name eq "PageRegion";
  3703.  
  3704.     # The command prototype should not be empty, set default
  3705.     if (!$cmd) {
  3706.         $cmd = "%s";
  3707.     }
  3708.  
  3709.     # Set default for missing section value
  3710.     if (defined($arg->{'style'}) && ($arg->{'style'} eq "J") &&
  3711.         !defined($arg->{'memberof'})) {
  3712.         $arg->{'section'} = "JCLSetup";
  3713.         } elsif (!defined($arg->{'section'})) {
  3714.         $arg->{'section'} = "AnySetup"
  3715.     }
  3716.     $section = $arg->{'section'};
  3717.  
  3718.     my $jcl = (($section eq 'JCLSetup') &&
  3719.            !defined($arg->{'memberof'}) ? "JCL" : "");
  3720.  
  3721.     # Set default for missing tranaslation/longname
  3722.     if (!$com) {$com = longname($name);}
  3723.  
  3724.     # If for a string option the default value is not available under
  3725.     # the enumerated choices, add it here. Make the default choice also
  3726.     # the first list entry
  3727.     if ($type =~ /^(string|password)$/) {
  3728.         $arg->{'default'} =
  3729.         checkoptionvalue($dat, $name, $arg->{'default'}, 1);
  3730.         # An empty string cannot be an option name in a PPD file,
  3731.         # use "None" in this case
  3732.         my $defcom = $arg->{'default'};
  3733.         my $defstr = $arg->{'default'};
  3734.         if ($arg->{'default'} !~ /\S/) {
  3735.         $arg->{'default'} = 'None';
  3736.         $defcom = '(None)';
  3737.         $defstr = '';
  3738.         } elsif ($arg->{'default'} eq 'None') {
  3739.         $defcom = '(None)';
  3740.         $defstr = '';
  3741.         } else {
  3742.         $arg->{'default'} =~ s/\W+/_/g;
  3743.         $arg->{'default'} =~ s/^_+|_+$//g;
  3744.         $arg->{'default'} = '_' if ($arg->{'default'} eq '');
  3745.             $defcom =~ s/:/ /g;
  3746.         $defcom =~ s/^ +| +$//g;
  3747.         }
  3748.         $default = $arg->{'default'};
  3749.         # Generate a new choice
  3750.         if (!defined($arg->{'vals_byname'}{$arg->{'default'}})) {
  3751.         checksetting($dat, $name, $arg->{'default'});
  3752.         my $newchoice = $arg->{'vals_byname'}{$arg->{'default'}};
  3753.         $newchoice->{'value'} = $arg->{'default'};
  3754.         $newchoice->{'comment'} = $defcom;
  3755.         $newchoice->{'driverval'} = $defstr;
  3756.         }
  3757.         # Bring the default entry to the first position
  3758.         my $index = 0;
  3759.         for (my $i = 0; $i <= $#{$arg->{vals}}; $i ++) {
  3760.         if ($arg->{vals}[$i]{'value'} eq $arg->{'default'}) {
  3761.             $index = $i;
  3762.             last;
  3763.         }
  3764.         }
  3765.         my $def = splice(@{$arg->{vals}}, $index, 1);
  3766.         unshift(@{$arg->{vals}}, $def);
  3767.     }
  3768.  
  3769.     # Do we have to open or close one or more groups here?
  3770.     # No group will be opened more than once, since the options
  3771.     # are sorted to have the members of every group together
  3772.  
  3773.     # Only take into account the groups of options which will be
  3774.     # visible user interface options in the PPD.
  3775.     if ((($type !~ /^(enum|string|password)$/) ||
  3776.          ($#{$arg->{'vals'}} > 0) || ($name eq "PageSize") ||
  3777.          ($arg->{'style'} eq 'G')) &&
  3778.         (!$arg->{'hidden'})) {
  3779.         # Find the level on which the group path of the current option
  3780.         # (@group) differs from the group path of the last option
  3781.         # (@groupstack).
  3782.         my $level = 0;
  3783.         while (($level <= $#groupstack) and
  3784.            ($level <= $#group) and 
  3785.            ($groupstack[$level] eq $group[$level])) {
  3786.         $level++;
  3787.         }
  3788.         for (my $i = $#groupstack; $i >= $level; $i--) {
  3789.         # Close this group, the current option is not member
  3790.         # of it.
  3791.         push(@optionblob,
  3792.              sprintf("\n*Close%sGroup: %s\n",
  3793.                  ($i > 0 ? "Sub" : ""), $groupstack[$i])
  3794.              );
  3795.         pop(@groupstack);
  3796.         }
  3797.         for (my $i = $level; $i <= $#group; $i++) {
  3798.         # Open this group, the current option is a member
  3799.         # of it.
  3800.         push(@optionblob,
  3801.              sprintf("\n*Open%sGroup: %s/%s\n",
  3802.                  ($i > 0 ? "Sub" : ""), $group[$i], 
  3803.                  cutguiname(longname($group[$i]), $shortgui))
  3804.              );
  3805.         push(@groupstack, $group[$i]);
  3806.         }
  3807.     }
  3808.  
  3809.     if ($type =~ /^(enum|string|password)$/) {
  3810.         # Extra information for string options
  3811.         my ($stringextralines0, $stringextralines1) = ('', '');
  3812.         if ($type =~ /^(string|password)$/) {
  3813.         $stringextralines0 .= sprintf
  3814.              ("*FoomaticRIPOption %s: %s %s %s\n",
  3815.               $name, $type, $optstyle, $spot);
  3816.         my $header = sprintf
  3817.             ("*FoomaticRIPOptionPrototype %s",
  3818.              $name);
  3819.         my $foomaticstr = ripdirective($header, $cmd) . "\n";
  3820.         $stringextralines1 .= $foomaticstr;
  3821.         # Stuff to insert into command line/job is more than one
  3822.         # line? Let an "*End" line follow
  3823.         if ($foomaticstr =~ /\n.*\n/s) {
  3824.             $stringextralines1 .= "*End\n";
  3825.         }
  3826.  
  3827.         if ($arg->{'maxlength'}) {
  3828.             $stringextralines1 .= sprintf
  3829.              ("*FoomaticRIPOptionMaxLength %s: %s\n",
  3830.               $name, $arg->{'maxlength'});
  3831.         }
  3832.  
  3833.         if ($arg->{'allowedchars'}) {
  3834.             my $header = sprintf
  3835.             ("*FoomaticRIPOptionAllowedChars %s",
  3836.              $name);
  3837.             my $entrystr = ripdirective($header, 
  3838.                         $arg->{'allowedchars'}) . "\n";
  3839.             $stringextralines1 .= $entrystr;
  3840.             # Stuff to insert into command line/job is more than one
  3841.             # line? Let an "*End" line follow
  3842.             if ($entrystr =~ /\n.*\n/s) {
  3843.             $stringextralines1 .= "*End\n";
  3844.             }
  3845.         }
  3846.  
  3847.         if ($arg->{'allowedregexp'}) {
  3848.             my $header = sprintf
  3849.             ("*FoomaticRIPOptionAllowedRegExp %s",
  3850.              $name);
  3851.             my $entrystr = ripdirective($header, 
  3852.                         $arg->{'allowedregexp'}) .
  3853.                             "\n";
  3854.             $stringextralines1 .= $entrystr;
  3855.             # Stuff to insert into command line/job is more than one
  3856.             # line? Let an "*End" line follow
  3857.             if ($entrystr =~ /\n.*\n/s) {
  3858.             $stringextralines1 .= "*End\n";
  3859.             }
  3860.         }
  3861.  
  3862.         }
  3863.  
  3864.         # Skip zero or one choice arguments. Do not skip "PageSize",
  3865.         # since a PPD file without "PageSize" will break the CUPS
  3866.         # environment and also do not skip PostScript options. For
  3867.         # skipped options with one choice only "*Foomatic..."
  3868.         # definitions will be used. Skip also the hidden member
  3869.         # options of a forced composite option.
  3870.         if (((1 < scalar(@{$arg->{'vals'}})) ||
  3871.          ($name eq "PageSize") ||
  3872.          ($arg->{'style'} eq 'G')) &&
  3873.         (!$arg->{'hidden'}) &&
  3874.         (0 < scalar(@{$arg->{'vals'}}))) {
  3875.  
  3876.         push(@optionblob,
  3877.              sprintf("\n*${jcl}OpenUI *%s/%s: PickOne\n", $name, 
  3878.                  cutguiname($com, $shortgui)));
  3879.  
  3880.         if ($arg->{'style'} ne 'G' && 
  3881.             (($optstyle ne "JCL") || defined($arg->{'memberof'}))) {
  3882.             # For non-PostScript options insert line with option
  3883.             # properties
  3884.             push(@optionblob, sprintf
  3885.              ("*FoomaticRIPOption %s: %s %s %s\n",
  3886.               $name, $type, $optstyle, $spot));
  3887.         }
  3888.  
  3889.         if ($type =~ /^(string|password)$/) {
  3890.             # Extra information for string options
  3891.             push(@optionblob, $stringextralines0, $stringextralines1);
  3892.         }
  3893.  
  3894.         push(@optionblob,
  3895.              sprintf("*OrderDependency: %s %s *%s\n", 
  3896.                  $order, $section, $name),
  3897.              sprintf("*Default%s: %s\n", 
  3898.                  $name,
  3899.                  (defined($default) ? 
  3900.                   checkoptionvalue($dat, $name, $default, 1) :
  3901.                   'Unknown')));
  3902.  
  3903.         if (!defined($default)) {
  3904.             my $whr = sprintf("%s %s driver %s",
  3905.                       $dat->{'make'},
  3906.                       $dat->{'model'},
  3907.                       $dat->{'driver'});
  3908.             warn "undefined default for $idx/$name on a $whr\n";
  3909.         }
  3910.         
  3911.         # If this is the page size argument; construct
  3912.         # PageRegion, ImageableArea, and PaperDimension clauses 
  3913.         # from it. Arguably this is all backwards, but what can
  3914.         # you do! ;)
  3915.         my @pageregion;
  3916.         my @imageablearea;
  3917.         my @paperdimension;
  3918.  
  3919.         # If we have a paper size named "Custom", or one with
  3920.         # one or both dimensions being zero, we must replace
  3921.         # this by an Adobe-complient custom paper size
  3922.         # definition.
  3923.         my $hascustompagesize = 0;
  3924.  
  3925.         # We take very big numbers now, to not impose limits.
  3926.         # Later, when we will have physical demensions of the
  3927.         # printers in the database.
  3928.         my $maxpagewidth = 100000;
  3929.         my $maxpageheight = 100000;
  3930.  
  3931.         # Start the PageRegion, ImageableArea, and PaperDimension
  3932.         # clauses
  3933.         if ($name eq "PageSize") {
  3934.             
  3935.             push(@pageregion,
  3936.              "*${jcl}OpenUI *PageRegion: PickOne
  3937. *OrderDependency: $order $section *PageRegion
  3938. *DefaultPageRegion: $dat->{'args_byname'}{'PageSize'}{'default'}");
  3939.             push(@imageablearea, 
  3940.              "*DefaultImageableArea: $dat->{'args_byname'}{'PageSize'}{'default'}");
  3941.             push(@paperdimension, 
  3942.              "*DefaultPaperDimension: $dat->{'args_byname'}{'PageSize'}{'default'}");
  3943.         }
  3944.  
  3945.         for my $v (@{$arg->{'vals'}}) {
  3946.             my $psstr = "";
  3947.  
  3948.             if ($name eq "PageSize") {
  3949.             
  3950.             my $value = $v->{'value'}; # in a PPD, the value 
  3951.                                        # is the PPD name...
  3952.             my $comment = $v->{'comment'};
  3953.  
  3954.             # Here we have to fill in the absolute sizes of the 
  3955.             # papers. We consult a table when we could not read
  3956.             # the sizes out of the choices of the "PageSize"
  3957.             # option.
  3958.             my $size = $v->{'driverval'};
  3959.             if ($size =~ /([\d\.]+)x([\d\.]+)([a-z]+)\b/) {
  3960.                 # 2 positive integers separated by 
  3961.                 # an 'x' with a unit
  3962.                 my $w = $1;
  3963.                 my $h = $2;
  3964.                 my $u = $3;
  3965.                 if ($u =~ /^in(|ch(|es))$/i) {
  3966.                 $w *= 72.0;
  3967.                 $h *= 72.0;
  3968.                 } elsif ($u =~ /^mm$/i) {
  3969.                 $w *= 72.0/25.4;
  3970.                 $h *= 72.0/25.4;
  3971.                 } elsif ($u =~ /^cm$/i) {
  3972.                 $w *= 72.0/2.54;
  3973.                 $h *= 72.0/2.54;
  3974.                 }
  3975.                 $w = sprintf("%.2f", $w) if $w =~ /\./;
  3976.                 $h = sprintf("%.2f", $h) if $h =~ /\./;
  3977.                 $size = "$w $h";
  3978.             } elsif (($size =~ /(\d+)[x\s]+(\d+)/) ||
  3979.                 # 2 positive integers separated by 
  3980.                 # whitespace or an 'x'
  3981.                  ($size =~ /\-dDEVICEWIDTHPOINTS\=(\d+)\s+\-dDEVICEHEIGHTPOINTS\=(\d+)/)) {
  3982.                 # "-dDEVICEWIDTHPOINTS=..."/"-dDEVICEHEIGHTPOINTS=..."
  3983.                 $size = "$1 $2";
  3984.             } else {
  3985.                 $size = getpapersize($value);
  3986.             }
  3987.             $size =~ /^\s*([\d\.]+)\s+([\d\.]+)\s*$/;
  3988.             my $width = $1;
  3989.             my $height = $2;
  3990.             if ($maxpagewidth < $width) {
  3991.                 $maxpagewidth = $width;
  3992.             }
  3993.             if ($maxpageheight < $height) {
  3994.                 $maxpageheight = $height;
  3995.             }
  3996.             if (($value eq "Custom") ||
  3997.                 ($width == 0) || ($height == 0)) {
  3998.                 # This page size is either named "Custom" or
  3999.                 # at least one of its dimensions is not fixed
  4000.                 # (=0), so this printer/driver combo must
  4001.                 # support custom page sizes
  4002.                 $hascustompagesize = 1;
  4003.                 # We do not add this size to the PPD file
  4004.                 # because the Adobe standard foresees a
  4005.                 # special code block in the header of the
  4006.                 # PPD file to be inserted when a custom
  4007.                 # page size is requested.
  4008.                 next;
  4009.             }
  4010.             # Determine the unprintable margins
  4011.             # Zero margins when no margin info exists
  4012.             my ($left, $right, $top, $bottom) =
  4013.                 getmargins($dat, $width, $height, $value);
  4014.             # Insert margins in "*ImageableArea" line
  4015.             push(@imageablearea,
  4016.                  "*ImageableArea $value/$comment: " . 
  4017.                  "\"$left $bottom $right $top\"");
  4018.             push(@paperdimension,
  4019.                  "*PaperDimension $value/$comment: \"$size\"");
  4020.             }
  4021.             my $foomaticstr = "";
  4022.             # For PostScript options PostScript code must be 
  4023.             # inserted, unless they are member of a composite
  4024.             # option AND they are set to the "Controlled by
  4025.             # '<Composite>'" choice (driverval is "\x01")
  4026.             if (($arg->{'style'} eq 'G' || 
  4027.              (($optstyle eq "JCL") &&
  4028.               !defined($arg->{'memberof'}))) &&
  4029.             ($v->{'driverval'} ne "\x01")) {
  4030.             # Ghostscript argument; offer up ps for
  4031.             # insertion
  4032.             my $sprintfcmd = $cmd;
  4033.             if ($optstyle eq "JCL") {
  4034.                 if ($sprintfcmd !~ m/^@/) {
  4035.                 $sprintfcmd = "\@PJL " . $sprintfcmd;
  4036.                 }
  4037.                 if ($sprintfcmd !~ m/<0A>$/) {
  4038.                 $sprintfcmd = $sprintfcmd . "<0A>";
  4039.                 }
  4040.             }
  4041.             $sprintfcmd =~ s/\%(?!s)/\%\%/g;
  4042.             $psstr = sprintf($sprintfcmd, 
  4043.                      (defined($v->{'driverval'})
  4044.                       ? $v->{'driverval'}
  4045.                       : $v->{'value'}));
  4046.             } else {
  4047.             # Option setting directive for Foomatic filter
  4048.             # 4 "%" because of the "sprintf" applied to it
  4049.             # In the end stay 2 "%" to have a PostScript 
  4050.             # comment
  4051.             $psstr = sprintf
  4052.                 ("%%%% FoomaticRIPOptionSetting: %s=%s",
  4053.                  $name, $v->{'value'});
  4054.             if ($v->{'driverval'} eq "\x01") {
  4055.                 # Only set the $foomaticstr when the selected
  4056.                 # choice is not the "Controlled by
  4057.                 # '<Composite>'" of a member of a collective
  4058.                 # option. Otherwise leave it out and let
  4059.                 # the value in the "FoomaticRIPOptionSetting"
  4060.                 # comment be "@<Composite>".
  4061.                 $psstr =~ s/=From/=\@/;
  4062.                 $foomaticstr = "";
  4063.             } else {
  4064.                 my $header = sprintf
  4065.                 ("*FoomaticRIPOptionSetting %s=%s",
  4066.                  $name, $v->{'value'});
  4067.                 my $sprintfcmd = $cmd;
  4068.                 $sprintfcmd =~ s/\%(?!s)/\%\%/g;
  4069.                 my $cmdval =
  4070.                 sprintf($sprintfcmd,
  4071.                     (defined($v->{'driverval'})
  4072.                      ? $v->{'driverval'}
  4073.                      : $v->{'value'}));
  4074.                 $foomaticstr = ripdirective($header, $cmdval) . 
  4075.                 "\n";
  4076.             }
  4077.             }
  4078.             # Make sure that the longname/translation exists
  4079.             if (!$v->{'comment'}) {
  4080.             if ($type !~ /^(string|password)$/) {
  4081.                 $v->{'comment'} = longname($v->{'value'});
  4082.             } else {
  4083.                 $v->{'comment'} = $v->{'value'};
  4084.             }
  4085.             }
  4086.             # Code supposed to be inserted into the PostScript
  4087.             # data when this choice is selected.
  4088.             push(@optionblob,
  4089.              sprintf("*%s %s/%s: \"%s\"\n", 
  4090.                  $name, $v->{'value'},
  4091.                  cutguiname($v->{'comment'}, $shortgui),
  4092.                  $psstr));
  4093.             # PostScript code is more than one line? Let an "*End"
  4094.             # line follow
  4095.             if ($psstr =~ /\n/s) {
  4096.             push(@optionblob, "*End\n");
  4097.             }
  4098.             # If we have a command line or JCL option, insert the
  4099.             # code here. For security reasons command line snippets
  4100.             # cannot be inserted into the "official" choice entry,
  4101.             # otherwise the appropriate RIP filter could execute
  4102.             # arbitrary code.
  4103.             push(@optionblob, $foomaticstr);
  4104.             # Stuff to insert into command line/job is more than one
  4105.             # line? Let an "*End" line follow
  4106.             if ($foomaticstr =~ /\n.*\n/s) {
  4107.             push(@optionblob, "*End\n");
  4108.             }
  4109.             # In modern PostScript interpreters "PageRegion" 
  4110.             # and "PageSize" are the same option, so we fill 
  4111.             # in the "PageRegion" the same
  4112.             # way as the "PageSize" choices.
  4113.             if ($name eq "PageSize") {
  4114.             push(@pageregion,
  4115.                  sprintf("*PageRegion %s/%s: \"%s\"", 
  4116.                      $v->{'value'}, $v->{'comment'},
  4117.                      $psstr));
  4118.             if ($psstr =~ /\n/s) {
  4119.                 push(@pageregion, "*End");
  4120.             }
  4121.             }
  4122.         }
  4123.         
  4124.         push(@optionblob,
  4125.              sprintf("*${jcl}CloseUI: *%s\n", $name));
  4126.  
  4127.                  # Insert Custom Option
  4128.         if ($type =~ /^(string|password)$/) {
  4129.             my $templ = $cmd;
  4130.             if ($optstyle eq "JCL") {
  4131.             $templ =~ s/%s/\\1/;
  4132.             if ($templ !~ m/^@/) {
  4133.                 $templ = "\@PJL " . $templ;
  4134.             }
  4135.             if ($templ !~ m/<0A>$/) {
  4136.                 $templ = $templ . "<0A>";
  4137.             }
  4138.             }
  4139.             elsif ($optstyle eq "CmdLine") {
  4140.             $templ = " pop ";
  4141.             }
  4142.             else {
  4143.             my $cnt = 0;
  4144.             my @words = split(/[ <>]/, $cmd);
  4145.             foreach my $word (@words) {
  4146.                 last if ($word eq '%s');
  4147.                 $cnt++ if ($word);
  4148.             }
  4149.             $templ =~ s/%s/ ${cnt} 1 roll /;
  4150.             }
  4151.             push(@optionblob, sprintf("*Custom%s%s True: \"%s\"\n", $jcl, $name, $templ));
  4152.             push(@optionblob,
  4153.             sprintf("*ParamCustom%s%s %s/%s: 1 %s 0 %d\n\n",
  4154.                 $jcl, $name, $name, $arg->{'comment'},
  4155.                 $type, $arg->{'maxlength'}));
  4156.         }
  4157.  
  4158.         if ($name eq "PageSize") {
  4159.             # Close the PageRegion, ImageableArea, and 
  4160.             # PaperDimension clauses
  4161.             push(@pageregion,
  4162.              "*${jcl}CloseUI: *PageRegion");
  4163.  
  4164.             my $paperdim = join("\n", 
  4165.                     ("", @pageregion, "", 
  4166.                      @imageablearea, "",
  4167.                      @paperdimension, ""));
  4168.             push (@optionblob, $paperdim);
  4169.  
  4170.             # Make the header entries for a custom page size
  4171.             if ($hascustompagesize) {
  4172.             my $maxpaperdim = 
  4173.                 ($maxpageheight > $maxpagewidth ?
  4174.                  $maxpageheight : $maxpagewidth);
  4175.             # PostScript code from the example 6 in section 6.3
  4176.             # of Adobe's PPD V4.3 specification
  4177.             # http://partners.adobe.com/asn/developer/pdfs/tn/5003.PPD_Spec_v4.3.pdf
  4178.             # If the page size is an option for the command line
  4179.             # of Ghostscript, let the values which where put
  4180.             # on the stack being popped and inserta comment
  4181.             # to advise the filter
  4182.             
  4183.             my $pscode;
  4184.             my $foomaticstr = "";
  4185.             if ($arg->{'style'} eq 'G') {
  4186.                 $pscode = "pop pop pop
  4187. <</PageSize [ 5 -2 roll ] /ImagingBBox null>>setpagedevice";
  4188.             } else {
  4189.                 my $a = $arg->{'vals_byname'}{'Custom'};
  4190.                 my $header = sprintf
  4191.                 ("*FoomaticRIPOptionSetting %s=%s",
  4192.                  $name, $a->{'value'});
  4193.                 my $sprintfcmd = $cmd;
  4194.                 $sprintfcmd =~ s/\%(?!s)/\%\%/g;
  4195.                 my $cmdval =
  4196.                 sprintf($sprintfcmd,
  4197.                     (defined($a->{'driverval'})
  4198.                      ? $a->{'driverval'}
  4199.                      : $a->{'value'}));
  4200.                 $foomaticstr =
  4201.                 ripdirective($header, $cmdval) . "\n";
  4202.                 # Stuff to insert into command line/job is more
  4203.                 # than one line? Let an "*End" line follow
  4204.                 if ($foomaticstr =~ /\n.*\n/s) {
  4205.                 $foomaticstr .= "*End\n";
  4206.                 }
  4207.                 $pscode = "pop pop pop pop pop
  4208. %% FoomaticRIPOptionSetting: $name=Custom";
  4209.             }
  4210.             my ($left, $right, $top, $bottom) =
  4211.                 getmargins($dat, 0, 0, 'Custom');
  4212.             my $custompagesizeheader = 
  4213. "*HWMargins: $left $bottom $right $top
  4214. *VariablePaperSize: True
  4215. *MaxMediaWidth: $maxpaperdim
  4216. *MaxMediaHeight: $maxpaperdim
  4217. *NonUIOrderDependency: $order $section *CustomPageSize
  4218. *CustomPageSize True: \"$pscode\"
  4219. *End
  4220. ${foomaticstr}*ParamCustomPageSize Width: 1 points 36 $maxpagewidth
  4221. *ParamCustomPageSize Height: 2 points 36 $maxpageheight
  4222. *ParamCustomPageSize Orientation: 3 int 0 0
  4223. *ParamCustomPageSize WidthOffset: 4 points 0 0
  4224. *ParamCustomPageSize HeightOffset: 5 points 0 0
  4225.  
  4226. ";
  4227.             
  4228.             unshift (@optionblob, $custompagesizeheader);
  4229.             } else {
  4230.             unshift (@optionblob,
  4231.                  "*VariablePaperSize: False\n\n");
  4232.             }
  4233.         }
  4234.         } elsif (((1 == scalar(@{$arg->{'vals'}})) &&
  4235.               ($arg->{'style'} ne 'G')) ||
  4236.              ($arg->{'hidden'})) {
  4237.         # non-PostScript enumerated choice option with one single 
  4238.         # choice or hidden member option of forced composite
  4239.         # option
  4240.  
  4241.         # Insert line with option properties
  4242.         my $foomaticstrs = '';
  4243.         for my $v (@{$arg->{'vals'}}) {
  4244.             my $header = sprintf
  4245.             ("*FoomaticRIPOptionSetting %s=%s",
  4246.              $name, $v->{'value'});
  4247.             my $cmdval = '';
  4248.             # For the "From<Composite>" setting the command line
  4249.             # value is not made use of, so leave it blank then.
  4250.             if ($v->{'driverval'} ne "\x01") {
  4251.             my $sprintfcmd = $cmd;
  4252.             $sprintfcmd =~ s/\%(?!s)/\%\%/g;
  4253.             $cmdval =
  4254.                 sprintf($sprintfcmd,
  4255.                     (defined($v->{'driverval'})
  4256.                      ? $v->{'driverval'}
  4257.                      : $v->{'value'}));
  4258.             }
  4259.             my $foomaticstr = ripdirective($header, $cmdval) . "\n";
  4260.             # Stuff to insert into command line/job is more
  4261.             # than one line? Let an "*End" line follow
  4262.             if ($foomaticstr =~ /\n.*\n/s) {
  4263.             $foomaticstr .= "*End\n";
  4264.             }
  4265.             $foomaticstrs .= $foomaticstr;
  4266.         }
  4267.         push(@optionblob, sprintf
  4268.              ("\n*FoomaticRIPOption %s: %s %s %s %s\n",
  4269.               $name, $type, $optstyle, $spot, $order),
  4270.              $stringextralines1, $foomaticstrs);
  4271.         }
  4272.     } elsif ($type eq 'bool') {
  4273.         my $name = $arg->{'name'};
  4274.         my $namef = $arg->{'name_false'};
  4275.         my $defstr = ($default ? 'True' : 'False');
  4276.         if (!defined($default)) { 
  4277.         $defstr = 'Unknown';
  4278.         }
  4279.         my $psstr = "";
  4280.         my $psstrf = "";
  4281.  
  4282.         push(@optionblob,
  4283.          sprintf("\n*${jcl}OpenUI *%s/%s: Boolean\n", $name, 
  4284.              cutguiname($com, $shortgui)));
  4285.  
  4286.         if ($arg->{'style'} eq 'G' || $optstyle eq "JCL") {
  4287.         # Ghostscript argument
  4288.         $psstr = $cmd;
  4289.         # Boolean options should not use the "%s" default for $cmd
  4290.         $psstr =~ s/^%s$//;
  4291.  
  4292.         if ($optstyle eq "JCL") {
  4293.             if ($psstr !~ m/^@/) {
  4294.             $psstr = "\@PJL " . $psstr;
  4295.             }
  4296.             if ($psstr !~ m/<0A>$/) {
  4297.             $psstr = $psstr . "<0A>";
  4298.             }
  4299.         }
  4300.         } else {
  4301.         # Option setting directive for Foomatic filter
  4302.         # 4 "%" because of the "sprintf" applied to it
  4303.         # In the end stay 2 "%" to have a PostScript comment
  4304.         my $header = sprintf
  4305.             ("%%%% FoomaticRIPOptionSetting: %s", $name);
  4306.         $psstr = "$header=True";
  4307.         $psstrf = "$header=False";
  4308.         $header = sprintf
  4309.             ("*FoomaticRIPOptionSetting %s", $name);
  4310.         my $foomaticstr = ripdirective($header, $cmd) . "\n";
  4311.         # For non-PostScript options insert line with option
  4312.         # properties
  4313.         push(@optionblob, sprintf
  4314.              ("*FoomaticRIPOption %s: bool %s %s\n",
  4315.               $name, $optstyle, $spot).
  4316.              $foomaticstr,
  4317.              ($foomaticstr =~ /\n.*\n/s ? "*End\n" : ""));
  4318.         }
  4319.  
  4320.         push(@optionblob,
  4321.          sprintf("*OrderDependency: %s %s *%s\n", 
  4322.              $order, $section, $name),
  4323.          sprintf("*Default%s: $defstr\n", $name),
  4324.          sprintf("*%s True/%s: \"%s\"\n", $name, 
  4325.              cutguiname($name, $shortgui), $psstr),
  4326.          ($psstr =~ /\n/s ? "*End\n" : ""),
  4327.          sprintf("*%s False/%s: \"%s\"\n", $name,
  4328.              cutguiname($namef, $shortgui), $psstrf),
  4329.          ($psstrf =~ /\n/s ? "*End\n" : ""),
  4330.          sprintf("*${jcl}CloseUI: *%s\n", $name));
  4331.         
  4332.     } elsif ($type eq 'int') {
  4333.  
  4334.         # Real numerical options do not exist in the Adobe
  4335.         # specification for PPD files. So we map the numerical
  4336.         # options to enumerated options offering the minimum, the
  4337.         # maximum, the default, and some values inbetween to the
  4338.         # user.
  4339.  
  4340.         my $min = $arg->{'min'};
  4341.         my $max = $arg->{'max'};
  4342.         my $second = $min + 1;
  4343.         my $stepsize = 1;
  4344.         if (($max - $min > 100) && ($name ne "Copies")) {
  4345.         # We don't want to have more than 100 values, but when the
  4346.         # difference between min and max is more than 100 we should
  4347.         # have at least 10 steps.
  4348.         my $mindesiredvalues = 10;
  4349.         my $maxdesiredvalues = 100;
  4350.         # Find the order of magnitude of the value range
  4351.         my $rangesize = $max - $min;
  4352.         my $log10 = log(10.0);
  4353.         my $rangeom = POSIX::floor(log($rangesize)/$log10);
  4354.         # Now find the step size
  4355.         my $trialstepsize = 10 ** $rangeom;
  4356.         my $numvalues = 0;
  4357.         while (($numvalues <= $mindesiredvalues) &&
  4358.                ($trialstepsize > 2)) {
  4359.             $trialstepsize /= 10;
  4360.             $numvalues = $rangesize/$trialstepsize;
  4361.         }
  4362.         # Try to find a finer stepping
  4363.         $stepsize = $trialstepsize;
  4364.         $trialstepsize = $stepsize / 2;
  4365.         $numvalues = $rangesize/$trialstepsize;
  4366.         if ($numvalues <= $maxdesiredvalues) {
  4367.             if ($stepsize > 20) { 
  4368.             $trialstepsize = $stepsize / 4;
  4369.             $numvalues = $rangesize/$trialstepsize;
  4370.             }
  4371.             if ($numvalues <= $maxdesiredvalues) {
  4372.             $trialstepsize = $stepsize / 5;
  4373.             $numvalues = $rangesize/$trialstepsize;
  4374.             }
  4375.             if ($numvalues <= $maxdesiredvalues) {
  4376.             $stepsize = $trialstepsize;
  4377.             } else {
  4378.             $stepsize /= 2;
  4379.             }
  4380.         }
  4381.         $numvalues = $rangesize/$stepsize;
  4382.         # We have the step size. Now we must find an appropriate
  4383.         # second value for the value list, so that it contains
  4384.         # the integer multiples of 10, 100, 1000, ...
  4385.         $second = $stepsize * POSIX::ceil($min / $stepsize);
  4386.         if ($second <= $min) {$second += $stepsize};
  4387.         }
  4388.         # Generate the choice list
  4389.         my @choicelist;
  4390.         push (@choicelist, $min);
  4391.         if (($default < $second) && ($default > $min)) {
  4392.         push (@choicelist, $default);
  4393.         }
  4394.         my $item = $second;
  4395.         while ($item < $max) {
  4396.         push (@choicelist, $item);
  4397.         if (($default < $item + $stepsize) && ($default > $item) &&
  4398.             ($default < $max)) {
  4399.             push (@choicelist, $default);
  4400.         }
  4401.         $item += $stepsize;
  4402.         }
  4403.         push (@choicelist, $max);
  4404.  
  4405.             # Add the option
  4406.  
  4407.         # Skip zero or one choice arguments
  4408.         if (1 < scalar(@choicelist)) {
  4409.         push(@optionblob,
  4410.              sprintf("\n*${jcl}OpenUI *%s/%s: PickOne\n", $name,
  4411.                  cutguiname($com, $shortgui)));
  4412.  
  4413.         # Insert lines with the special properties of a
  4414.         # numerical option. Do this also for PostScript options
  4415.         # because numerical options are not supported by the PPD
  4416.         # file syntax. This way the info about this option being
  4417.         # a numerical one does not get lost
  4418.  
  4419.         push(@optionblob, sprintf
  4420.              ("*FoomaticRIPOption %s: int %s %s\n",
  4421.               $name, $optstyle, $spot));
  4422.  
  4423.         my $header = sprintf
  4424.             ("*FoomaticRIPOptionPrototype %s",
  4425.              $name);
  4426.         my $foomaticstr = ripdirective($header, $cmd) . "\n";
  4427.         push(@optionblob, $foomaticstr);
  4428.         # Stuff to insert into command line/job is more than one
  4429.         # line? Let an "*End" line follow
  4430.         if ($foomaticstr =~ /\n.*\n/s) {
  4431.             push(@optionblob, "*End\n");
  4432.         }
  4433.  
  4434.         push(@optionblob, sprintf
  4435.              ("*FoomaticRIPOptionRange %s: %s %s\n",
  4436.               $name, $arg->{'min'}, $arg->{'max'}));
  4437.  
  4438.         push(@optionblob,
  4439.              sprintf("*OrderDependency: %s %s *%s\n", 
  4440.                  $order, $section, $name),
  4441.              sprintf("*Default%s: %s\n", 
  4442.                  $name,
  4443.                  (defined($default) ? $default : 'Unknown')),
  4444.              sprintf("*FoomaticRIPDefault%s: %s\n", 
  4445.                  $name,
  4446.                  (defined($default) ? $default : 'Unknown')));
  4447.         if (!defined($default)) {
  4448.             my $whr = sprintf("%s %s driver %s",
  4449.                       $dat->{'make'},
  4450.                       $dat->{'model'},
  4451.                       $dat->{'driver'});
  4452.             warn "undefined default for $idx/$name on a $whr\n";
  4453.         }
  4454.         
  4455.         for my $v (@choicelist) {
  4456.             my $psstr = "";
  4457.             
  4458.             if ($optstyle eq "PS"|| $optstyle eq "JCL") {
  4459.             # Ghostscript argument; offer up ps for insertion
  4460.             my $sprintfcmd = $cmd;
  4461.             if ($optstyle eq "JCL") {
  4462.                 if ($sprintfcmd !~ m/^@/) {
  4463.                 $sprintfcmd = "\@PJL " . $sprintfcmd;
  4464.                 }
  4465.                 if ($sprintfcmd !~ m/<0A>$/) {
  4466.                 $sprintfcmd = $sprintfcmd . "<0A>";
  4467.                 }
  4468.             }
  4469.             $sprintfcmd =~ s/\%(?!s)/\%\%/g;
  4470.             $psstr = sprintf($sprintfcmd, $v);
  4471.             } else {
  4472.             # Option setting directive for Foomatic filter
  4473.             # 4 "%" because of the "sprintf" applied to it
  4474.             # In the end stay 2 "%" to have a PostScript comment
  4475.             $psstr = sprintf
  4476.                  ("%%%% FoomaticRIPOptionSetting: %s=%s",
  4477.                   $name, $v);
  4478.             }
  4479.             push(@optionblob,
  4480.              sprintf("*%s %s/%s: \"%s\"\n", 
  4481.                  $name, $v, 
  4482.                  cutguiname($v, $shortgui), $psstr));
  4483.             # PostScript code is more than one line? Let an "*End"
  4484.             # line follow
  4485.             if ($psstr =~ /\n/s) {
  4486.             push(@optionblob, "*End\n");
  4487.             }
  4488.         }
  4489.         
  4490.         push(@optionblob,
  4491.             sprintf("*${jcl}CloseUI: *%s\n\n", $name));
  4492.  
  4493.         # Insert custom option
  4494.         my $templ = $cmd;
  4495.         if ($optstyle eq "JCL") {
  4496.             $templ =~ s/%s/\\1/;
  4497.             if ($templ !~ m/^@/) {
  4498.             $templ = "\@PJL " . $templ;
  4499.             }
  4500.             if ($templ !~ m/<0A>$/) {
  4501.             $templ = $templ . "<0A>";
  4502.             }
  4503.         }
  4504.         elsif ($optstyle eq "CmdLine") {
  4505.             $templ = " pop ";
  4506.         }
  4507.         else {
  4508.             my $cnt = 0;
  4509.             my @words = split(/[ <>]/, $cmd);
  4510.             foreach my $word (@words) {
  4511.             last if ($word eq '%s');
  4512.             $cnt++ if ($word);
  4513.             }
  4514.             $templ =~ s/%s/ ${cnt} 1 roll /;
  4515.         }
  4516.         push(@optionblob, sprintf("*Custom%s%s True: \"%s\"\n", $jcl, $name, $templ));
  4517.         push(@optionblob,
  4518.             sprintf("*ParamCustom%s%s %s/%s: 1 int %d %d\n\n",
  4519.             $jcl, $name, $name, $arg->{'comment'}, $min, $max));
  4520.         }
  4521.     } elsif ($type eq 'float') {
  4522.  
  4523.         # Real numerical options do not exist in the Adobe
  4524.         # specification for PPD files. So we map the numerical
  4525.         # options to enumerated options offering the minimum, the
  4526.         # maximum, the default, and some values inbetween to the
  4527.         # user.
  4528.  
  4529.         my $min = $arg->{'min'};
  4530.         my $max = $arg->{'max'};
  4531.         # We don't want to have more than 500 values or less than 50
  4532.         # values.
  4533.         my $mindesiredvalues = 10;
  4534.         my $maxdesiredvalues = 100;
  4535.         # Find the order of magnitude of the value range
  4536.         my $rangesize = $max - $min;
  4537.         my $log10 = log(10.0);
  4538.         my $rangeom = POSIX::floor(log($rangesize)/$log10);
  4539.         # Now find the step size
  4540.         my $trialstepsize = 10 ** $rangeom;
  4541.         my $stepom = $rangeom; # Order of magnitude of stepsize,
  4542.                                # needed for determining necessary number
  4543.                                # of digits
  4544.         my $numvalues = 0;
  4545.         while ($numvalues <= $mindesiredvalues) {
  4546.         $trialstepsize /= 10;
  4547.         $stepom -= 1;
  4548.         $numvalues = $rangesize/$trialstepsize;
  4549.         }
  4550.         # Try to find a finer stepping
  4551.         my $stepsize = $trialstepsize;
  4552.         my $stepsizeorig = $stepsize;
  4553.         $trialstepsize = $stepsizeorig / 2;
  4554.         $numvalues = $rangesize/$trialstepsize;
  4555.         if ($numvalues <= $maxdesiredvalues) {
  4556.         $stepsize = $trialstepsize;
  4557.         $trialstepsize = $stepsizeorig / 4;
  4558.         $numvalues = $rangesize/$trialstepsize;
  4559.         if ($numvalues <= $maxdesiredvalues) {
  4560.             $stepsize = $trialstepsize;
  4561.             $trialstepsize = $stepsizeorig / 5;
  4562.             $numvalues = $rangesize/$trialstepsize;
  4563.             if ($numvalues <= $maxdesiredvalues) {
  4564.             $stepsize = $trialstepsize;
  4565.             }
  4566.         }
  4567.         }
  4568.         $numvalues = $rangesize/$stepsize;
  4569.         if ($stepsize < $stepsizeorig * 0.9) {$stepom -= 1;}
  4570.         # Determine number of digits after the decimal point for
  4571.         # formatting the output values.
  4572.         my $digits = 0;
  4573.         if ($stepom < 0) {
  4574.         $digits = - $stepom;
  4575.         }
  4576.         # We have the step size. Now we must find an appropriate
  4577.         # second value for the value list, so that it contains
  4578.         # the integer multiples of 10, 100, 1000, ...
  4579.         my $second = $stepsize * POSIX::ceil($min / $stepsize);
  4580.         if ($second <= $min) {$second += $stepsize};
  4581.         # Generate the choice list
  4582.         my @choicelist;
  4583.         my $choicestr =  sprintf("%.${digits}f", $min);
  4584.         push (@choicelist, $choicestr);
  4585.         if (($default < $second) && ($default > $min)) {
  4586.         $choicestr =  sprintf("%.${digits}f", $default);
  4587.         # Prevent values from entering twice because of rounding
  4588.         # inacuracy
  4589.         if ($choicestr ne $choicelist[$#choicelist]) {
  4590.             push (@choicelist, $choicestr);
  4591.         }
  4592.         }
  4593.         my $item = $second;
  4594.         my $i = 0;
  4595.         while ($item < $max) {
  4596.         $choicestr =  sprintf("%.${digits}f", $item);
  4597.         # Prevent values from entering twice because of rounding
  4598.         # inacuracy
  4599.         if ($choicestr ne $choicelist[$#choicelist]) {
  4600.             push (@choicelist, $choicestr);
  4601.         }
  4602.         if (($default < $item + $stepsize) && ($default > $item) &&
  4603.             ($default < $max)) {
  4604.             $choicestr =  sprintf("%.${digits}f", $default);
  4605.             # Prevent values from entering twice because of rounding
  4606.             # inacuracy
  4607.             if ($choicestr ne $choicelist[$#choicelist]) {
  4608.             push (@choicelist, $choicestr);
  4609.             }
  4610.         }
  4611.         $i += 1;
  4612.         $item = $second + $i * $stepsize;
  4613.         }
  4614.         $choicestr =  sprintf("%.${digits}f", $max);
  4615.         # Prevent values from entering twice because of rounding
  4616.         # inacuracy
  4617.         if ($choicestr ne $choicelist[$#choicelist]) {
  4618.         push (@choicelist, $choicestr);
  4619.         }
  4620.  
  4621.             # Add the option
  4622.  
  4623.         # Skip zero or one choice arguments
  4624.         if (1 < scalar(@choicelist)) {
  4625.         push(@optionblob,
  4626.              sprintf("\n*${jcl}OpenUI *%s/%s: PickOne\n", $name, 
  4627.                  cutguiname($com, $shortgui)));
  4628.  
  4629.         # Insert lines with the special properties of a
  4630.         # numerical option. Do this also for PostScript options
  4631.         # because numerical options are not supported by the PPD
  4632.         # file syntax. This way the info about this option being
  4633.         # a numerical one does not get lost
  4634.  
  4635.         push(@optionblob, sprintf
  4636.              ("*FoomaticRIPOption %s: float %s %s\n",
  4637.               $name, $optstyle, $spot));
  4638.  
  4639.         my $header = sprintf
  4640.             ("*FoomaticRIPOptionPrototype %s",
  4641.              $name);
  4642.         my $foomaticstr = ripdirective($header, $cmd) . "\n";
  4643.         push(@optionblob, $foomaticstr);
  4644.         # Stuff to insert into command line/job is more than one
  4645.         # line? Let an "*End" line follow
  4646.         if ($foomaticstr =~ /\n.*\n/s) {
  4647.             push(@optionblob, "*End\n");
  4648.         }
  4649.  
  4650.         push(@optionblob, sprintf
  4651.              ("*FoomaticRIPOptionRange %s: %s %s\n",
  4652.               $name, $arg->{'min'}, $arg->{'max'}));
  4653.  
  4654.         push(@optionblob,
  4655.              sprintf("*OrderDependency: %s %s *%s\n", 
  4656.                  $order, $section, $name),
  4657.              sprintf("*Default%s: %s\n", 
  4658.                  $name,
  4659.                  (defined($default) ? 
  4660.                   sprintf("%.${digits}f", $default) : 'Unknown')),
  4661.              sprintf("*FoomaticRIPDefault%s: %s\n", 
  4662.                  $name,
  4663.                  (defined($default) ? 
  4664.                   sprintf("%.${digits}f", $default) : 'Unknown')));
  4665.         if (!defined($default)) {
  4666.             my $whr = sprintf("%s %s driver %s",
  4667.                       $dat->{'make'},
  4668.                       $dat->{'model'},
  4669.                       $dat->{'driver'});
  4670.             warn "undefined default for $idx/$name on a $whr\n";
  4671.         }
  4672.  
  4673.         for my $v (@choicelist) {
  4674.             my $psstr = "";
  4675.             if ($arg->{'style'} eq 'G') {
  4676.             # Ghostscript argument; offer up ps for insertion
  4677.             my $sprintfcmd = $cmd;
  4678.             $sprintfcmd =~ s/\%(?!s)/\%\%/g;
  4679.             $psstr = sprintf($sprintfcmd, $v);
  4680.             } else {
  4681.             # Option setting directive for Foomatic filter
  4682.             # 4 "%" because of the "sprintf" applied to it
  4683.             # In the end stay 2 "%" to have a PostScript comment
  4684.             $psstr = sprintf
  4685.                  ("%%%% FoomaticRIPOptionSetting: %s=%s",
  4686.                   $name, $v);
  4687.             }
  4688.             push(@optionblob,
  4689.              sprintf("*%s %s/%s: \"%s\"\n", 
  4690.                  $name, $v, 
  4691.                  cutguiname($v, $shortgui), $psstr));
  4692.             # PostScript code is more than one line? Let an "*End"
  4693.             # line follow
  4694.             if ($psstr =~ /\n/s) {
  4695.             push(@optionblob, "*End\n");
  4696.             }
  4697.         }
  4698.         
  4699.         push(@optionblob,
  4700.              sprintf("*${jcl}CloseUI: *%s\n\n", $name));
  4701.  
  4702.         # Insert custom option
  4703.         my $templ = $cmd;
  4704.         if ($optstyle eq "JCL") {
  4705.             $templ =~ s/%s/\\1/;
  4706.             if ($templ !~ m/^@/) {
  4707.             $templ = "\@PJL " . $templ;
  4708.             }
  4709.             if ($templ !~ m/<0A>$/) {
  4710.             $templ = $templ . "<0A>";
  4711.             }
  4712.         }
  4713.         elsif ($optstyle eq "CmdLine") {
  4714.             $templ = " pop ";
  4715.         }
  4716.         else {
  4717.             my $cnt = 0;
  4718.             my @words = split(/[ <>]/, $cmd);
  4719.             foreach my $word (@words) {
  4720.             last if ($word eq '%s');
  4721.             $cnt++ if ($word);
  4722.             }
  4723.             $templ =~ s/%s/ ${cnt} 1 roll /;
  4724.         }
  4725.         push(@optionblob, sprintf("*Custom%s%s True: \"%s\"\n", $jcl, $name, $templ));
  4726.         push(@optionblob,
  4727.             sprintf("*ParamCustom%s%s %s/%s: 1 real %f %f\n\n",
  4728.             $jcl, $name, $name, $arg->{'comment'}, $min, $max));
  4729.  
  4730.         }
  4731.         }
  4732.     }
  4733.  
  4734.     # Close the option groups which are still open
  4735.     for (my $i = $#groupstack; $i >= 0; $i--) {
  4736.     push(@optionblob,
  4737.          sprintf("\n*Close%sGroup: %s\n",
  4738.              ($i > 0 ? "Sub" : ""), $groupstack[$i])
  4739.          );
  4740.     pop(@groupstack);
  4741.     }
  4742.  
  4743.     if (! $dat->{'args_byname'}{'PageSize'} ) {
  4744.     
  4745.     # This is a problem, since CUPS segfaults on PPD files without
  4746.     # a default PageSize set.  Indeed, the PPD spec requires a
  4747.     # PageSize clause.
  4748.     
  4749.     # Ghostscript does not understand "/PageRegion[...]", therefore
  4750.     # we use "/PageSize[...]" in the "*PageRegion" option here, in
  4751.     # addition, for most modern PostScript interpreters "PageRegion"
  4752.     # is the same as "PageSize".
  4753.  
  4754.     push(@optionblob, <<EOFPGSZ);
  4755.  
  4756. *% This is fake. We have no information on how to
  4757. *% set the pagesize for this driver in the database. To
  4758. *% prevent PPD users from blowing up, we must provide a
  4759. *% default pagesize value.
  4760.  
  4761. *OpenUI *PageSize/Media Size: PickOne
  4762. *OrderDependency: 10 AnySetup *PageSize
  4763. *DefaultPageSize: Letter
  4764. *PageSize Letter/Letter: "<</PageSize[612 792]/ImagingBBox null>>setpagedevice"
  4765. *PageSize Legal/Legal: "<</PageSize[612 1008]/ImagingBBox null>>setpagedevice"
  4766. *PageSize A4/A4: "<</PageSize[595 842]/ImagingBBox null>>setpagedevice"
  4767. *CloseUI: *PageSize
  4768.  
  4769. *OpenUI *PageRegion: PickOne
  4770. *OrderDependency: 10 AnySetup *PageRegion
  4771. *DefaultPageRegion: Letter
  4772. *PageRegion Letter/Letter: "<</PageSize[612 792]/ImagingBBox null>>setpagedevice"
  4773. *PageRegion Legal/Legal: "<</PageSize[612 1008]/ImagingBBox null>>setpagedevice"
  4774. *PageRegion A4/A4: "<</PageSize[595 842]/ImagingBBox null>>setpagedevice"
  4775. *CloseUI: *PageRegion
  4776.  
  4777. *DefaultImageableArea: Letter
  4778. *ImageableArea Letter/Letter:    "0 0 612 792"
  4779. *ImageableArea Legal/Legal:    "0 0 612 1008"
  4780. *ImageableArea A4/A4:    "0 0 595 842"
  4781.  
  4782. *DefaultPaperDimension: Letter
  4783. *PaperDimension Letter/Letter:    "612 792"
  4784. *PaperDimension Legal/Legal:    "612 1008"
  4785. *PaperDimension A4/A4:    "595 842"
  4786.  
  4787. EOFPGSZ
  4788.     }
  4789.  
  4790.     my @others;
  4791.  
  4792.     my $headcomment =
  4793. "*% For information on using this, and to obtain the required backend
  4794. *% script, consult http://www.openprinting.org/
  4795. *%
  4796. *% This file is published under the GNU General Public License
  4797. *%
  4798. *% PPD-O-MATIC (4.0.0 or newer) generated this PPD file. It is for use with 
  4799. *% all programs and environments which use PPD files for dealing with
  4800. *% printer capability information. The printer must be configured with the
  4801. *% \"foomatic-rip\" backend filter script of Foomatic 4.0.0 or newer. This 
  4802. *% file and \"foomatic-rip\" work together to support PPD-controlled printer
  4803. *% driver option access with all supported printer drivers and printing
  4804. *% spoolers.
  4805. *%
  4806. *% To save this file on your disk, wait until the download has completed
  4807. *% (the animation of the browser logo must stop) and then use the
  4808. *% \"Save as...\" command in the \"File\" menu of your browser or in the 
  4809. *% pop-up manu when you click on this document with the right mouse button.
  4810. *% DO NOT cut and paste this file into an editor with your mouse. This can
  4811. *% introduce additional line breaks which lead to unexpected results.";
  4812.  
  4813.     my $postpipe = "";
  4814.     if ($dat->{'postpipe'}) {
  4815.     my $header = "*FoomaticRIPPostPipe";
  4816.     my $code = $dat->{'postpipe'};
  4817.     $postpipe = ripdirective($header, $code) . "\n";
  4818.     if ($postpipe =~ /\n.*\n/s) {
  4819.         $postpipe .= "*End\n";
  4820.     }
  4821.     }
  4822.     my $opts = join('',@optionblob);
  4823.     my $otherstuff = join('',@others);
  4824.     my $pcfilename;
  4825.     if (($dat->{'pcmodel'}) && ($dat->{'pcdriver'})) {
  4826.     $pcfilename = uc("$dat->{'pcmodel'}$dat->{'pcdriver'}");
  4827.     } else {
  4828.     my $driver = $dat->{'driver'};
  4829.     $driver =~ m!(^(.{1,8}))!;
  4830.     $pcfilename = uc($1);
  4831.     }
  4832.     $pcfilename = 'FOOMATIC' if !defined($pcfilename);
  4833.     my $model = $dat->{'model'};
  4834.     my $make = $dat->{'make'};
  4835.     my ($ieee1284,$pnpmake,$pnpmodel,$filename,$longname,
  4836.     $drivername,$nickname,$modelname) =
  4837.         getppdheaderdata($dat, $dat->{'driver'}, $dat->{'recdriver'});
  4838.     if ($ieee1284) {
  4839.     $ieee1284 = "*1284DeviceID: \"" . $ieee1284 . "\"";
  4840.     }
  4841.  
  4842.     # Add info about driver properties
  4843.     my $drvproperties = "";
  4844.     $drvproperties .= "*driverName $dat->{'driver'}: \"" .
  4845.     ($dat->{'shortdescription'} ? 
  4846.      $dat->{'shortdescription'} : "") . 
  4847.      "\"\n" if defined($dat->{'driver'});
  4848.     $drvproperties .= "*driverType $dat->{'type'}" .
  4849.     ($dat->{'type'} eq "G" ? "/Ghostscript built-in" :
  4850.      ($dat->{'type'} eq "U" ? "/Ghostscript Uniprint" :
  4851.       ($dat->{'type'} eq "F" ? "/Filter" :
  4852.        ($dat->{'type'} eq "C" ? "/CUPS Raster" :
  4853.         ($dat->{'type'} eq "V" ? "/OpenPrinting Vector" :
  4854.          ($dat->{'type'} eq "I" ? "/IJS" :
  4855.           ($dat->{'type'} eq "P" ? "/PostScript" : ""))))))) . 
  4856.           ": \"\"\n" if defined($dat->{'type'});
  4857.     $drvproperties .= "*driverUrl: \"$dat->{'url'}\"\n" if
  4858.     defined($dat->{'url'});
  4859.     if ((defined($dat->{'obsolete'})) &&
  4860.     ($dat->{'obsolete'} ne "0")) {
  4861.     $drvproperties .= "*driverObsolete: True\n";
  4862.     if ($dat->{'obsolete'} ne "1") {
  4863.         $drvproperties .= "*driverRecommendedReplacement: " .
  4864.         "\"$dat->{'obsolete'}\"\n";
  4865.     }
  4866.     } else {
  4867.     $drvproperties .= "*driverObsolete: False\n";
  4868.     }
  4869.     $drvproperties .= "*driverSupplier: \"$dat->{'supplier'}\"\n" if
  4870.     defined($dat->{'supplier'});
  4871.     $drvproperties .= "*driverManufacturerSupplied: " . 
  4872.     ($dat->{'manufacturersupplied'} eq "1" ? "True" : 
  4873.      ($dat->{make} =~ m,^($dat->{'manufacturersupplied'})$,i ? "True" :
  4874.       "False")) . "\n" if
  4875.     defined($dat->{'manufacturersupplied'});
  4876.     $drvproperties .= "*driverLicense: \"$dat->{'license'}\"\n" if
  4877.     defined($dat->{'license'});
  4878.     $drvproperties .= "*driverFreeSoftware: " . 
  4879.     ($dat->{'free'} ? "True" : "False") . "\n" if
  4880.     defined($dat->{'free'});
  4881.     if (defined($dat->{'supportcontacts'})) {
  4882.     foreach my $entry (@{$dat->{'supportcontacts'}}) {
  4883.         my $uclevel = uc(substr($entry->{'level'}, 0, 1)) .
  4884.         lc(substr($entry->{'level'}, 1));
  4885.         $drvproperties .= "*driverSupportContact${uclevel}: " .
  4886.         "\"$entry->{'url'} $entry->{'description'}\"\n";
  4887.     }
  4888.     }
  4889.     if (defined($dat->{'drvmaxresx'}) || defined($dat->{'drvmaxresy'})) {
  4890.     my ($maxresx, $maxresy);
  4891.     $maxresx = $dat->{'drvmaxresx'} if defined($dat->{'drvmaxresx'});
  4892.     $maxresy = $dat->{'drvmaxresy'} if defined($dat->{'drvmaxresy'});
  4893.     $maxresx = $maxresy if !$maxresx;
  4894.     $maxresy = $maxresx if !$maxresy;
  4895.     $drvproperties .= "*driverMaxResolution: " .
  4896.         "${maxresx} ${maxresy}\n";
  4897.     }
  4898.     $drvproperties .= "*driverColor: " . 
  4899.     ($dat->{'drvcolor'} ? "True" : "False") . "\n" if
  4900.     defined($dat->{'drvcolor'});
  4901.     $drvproperties .= "*driverTextSupport: $dat->{'text'}\n" if
  4902.     defined($dat->{'text'});
  4903.     $drvproperties .= "*driverLineartSupport: $dat->{'lineart'}\n" if
  4904.     defined($dat->{'lineart'});
  4905.     $drvproperties .= "*driverGraphicsSupport: $dat->{'graphics'}\n" if
  4906.     defined($dat->{'graphics'});
  4907.     $drvproperties .= "*driverPhotoSupport: $dat->{'photo'}\n" if
  4908.     defined($dat->{'photo'});
  4909.     $drvproperties .= "*driverSystemmLoad: $dat->{'load'}\n" if
  4910.     defined($dat->{'load'});
  4911.     $drvproperties .= "*driverRenderingSpeed: $dat->{'speed'}\n" if
  4912.     defined($dat->{'speed'});
  4913.     $drvproperties = "\n$drvproperties" if $drvproperties;
  4914.  
  4915.     # Do not use "," or "+" in the *ShortNickName to make the Windows
  4916.     # PostScript drivers happy
  4917.     my $shortnickname = "$make $model $drivername";
  4918.     if (length($shortnickname) > 31) {
  4919.     # ShortNickName too long? Shorten it.
  4920.     my %parts;
  4921.     $parts{'make'} = $make;
  4922.     $parts{'model'} = $model;
  4923.     $parts{'driver'} = $drivername;
  4924.     # Go through the three components, begin with model name, then
  4925.     # make and then driver
  4926.     for my $part (qw/model make driver/) {
  4927.         # Split the component into words, cutting always at the right edge
  4928.         # of the word. Cut also at a capital in the middle of the word
  4929.         # (ex: "S" in "PostScript").
  4930.         my @words = split(/(?<=[a-zA-Z])(?![a-zA-Z])|(?<=[a-z])(?=[A-Z])/,
  4931.                   $parts{$part});
  4932.         # Go through all words
  4933.         for (@words) {
  4934.         # Do not abbreviate words of less than 4 letters
  4935.         next if ($_ !~ /[a-zA-Z]{4,}$/);
  4936.         # How many letters did we chop off
  4937.         my $abbreviated = 0;
  4938.             while (1) {
  4939.             # Remove the last letter
  4940.             chop;
  4941.             $abbreviated ++;
  4942.             # Build the shortened component ...
  4943.             $parts{$part} = join('', @words);
  4944.             # ... and the ShortNickName
  4945.             $shortnickname =
  4946.             "$parts{'make'} $parts{'model'} $parts{'driver'}";
  4947.             # Stop if the ShostNickName has 30 characters or less
  4948.             # (we have still to add the abbreviation point), if there
  4949.             # is only one letter left, or if the manufacturer name
  4950.             # is reduced to three characters. Do not accept an
  4951.             # abbreviation of one character, as, taking the
  4952.             # abbreviation point into account, it does not save
  4953.             # a character.
  4954.             last if (((length($shortnickname) <= 30) &&
  4955.                   ($abbreviated != 1)) ||
  4956.                  ($_ !~ /[a-zA-Z]{2,}$/) ||
  4957.                  ((length($parts{'make'}) <= 3) &&
  4958.                   ($abbreviated != 1)));
  4959.         }
  4960.         #Abbreviation point
  4961.         if ($abbreviated) {
  4962.             $_ .= '.';
  4963.         }
  4964.         $parts{$part} = join('', @words);
  4965.         $shortnickname =
  4966.             "$parts{'make'} $parts{'model'} $parts{'driver'}";
  4967.         last if (length($shortnickname) <= 31);
  4968.         }
  4969.         last if (length($shortnickname) <= 31);
  4970.     }
  4971.     while ((length($shortnickname) > 31) &&
  4972.            (length($parts{'model'}) > 3)) {
  4973.         # ShortNickName too long? Remove last words from model name.
  4974.         $parts{'model'} =~
  4975.         s/(?<=[a-zA-Z0-9])[^a-zA-Z0-9]+[a-zA-Z0-9]*$//;
  4976.         my $new =
  4977.         "$parts{'make'} $parts{'model'}, $parts{'driver'}";
  4978.         last if ($new eq $shortnickname);
  4979.         $shortnickname = $new;
  4980.     }
  4981.     if (length($shortnickname) > 31) {
  4982.         # If nothing else helps ...
  4983.         $shortnickname = substr($shortnickname, 0, 31);
  4984.     }
  4985.     }
  4986.  
  4987.     my $color;
  4988.     if ($dat->{'color'} &&
  4989.     (!defined($dat->{'drvcolor'}) || ($dat->{'drvcolor'} != 0))) {
  4990.     $color = "*ColorDevice:    True\n*DefaultColorSpace: RGB";
  4991.     } else {
  4992.     $color = "*ColorDevice:    False\n*DefaultColorSpace: Gray";
  4993.     }
  4994.  
  4995.     # Clean up "<ppdentry>"s
  4996.     foreach my $type ('printerppdentry', 'driverppdentry', 'comboppdentry'){
  4997.     if (defined($dat->{$type})) {
  4998.         $dat->{$type} =~ s/^\s+//gm;
  4999.         $dat->{$type} =~ s/\s+$//gm;
  5000.         $dat->{$type} =~ s/^\n+//gs;
  5001.         $dat->{$type} =~ s/\n*$/\n/gs;
  5002.     } else {
  5003.         $dat->{$type} = '';
  5004.     }
  5005.     }
  5006.     my $extralines = ($dat->{'comboppdentry'} ?
  5007.               $dat->{'comboppdentry'} :
  5008.               $dat->{'printerppdentry'} .
  5009.               $dat->{'driverppdentry'});
  5010.  
  5011.     my $tmpl = get_tmpl();
  5012.     $tmpl =~ s!\@\@POSTPIPE\@\@!$postpipe!g;
  5013.     $tmpl =~ s!\@\@HEADCOMMENT\@\@!$headcomment!g;
  5014.     $tmpl =~ s!\@\@SAVETHISAS\@\@!$longname!g;
  5015.     $tmpl =~ s!\@\@PCFILENAME\@\@!$pcfilename!g;
  5016.     $tmpl =~ s!\@\@MANUFACTURER\@\@!$make!g;
  5017.     $tmpl =~ s!\@\@PNPMAKE\@\@!$pnpmake!g;
  5018.     $tmpl =~ s!\@\@PNPMODEL\@\@!$pnpmodel!g;
  5019.     $tmpl =~ s!\@\@MODEL\@\@!$modelname!g;
  5020.     $tmpl =~ s!\@\@NICKNAME\@\@!$nickname!g;
  5021.     $tmpl =~ s!\@\@SHORTNICKNAME\@\@!$shortnickname!g;
  5022.     $tmpl =~ s!\@\@COLOR\@\@!$color!g;
  5023.     $tmpl =~ s!\@\@IEEE1284\@\@!$ieee1284!g;
  5024.     $tmpl =~ s!\@\@DRIVERPROPERTIES\@\@!$drvproperties!g;
  5025.     $tmpl =~ s!\@\@OTHERSTUFF\@\@!$otherstuff!g;
  5026.     $tmpl =~ s!\@\@OPTIONS\@\@!$opts!g;
  5027.     $tmpl =~ s!\@\@EXTRALINES\@\@!$extralines!g;
  5028.     
  5029.     return ($tmpl);
  5030. }
  5031.  
  5032.  
  5033. # Utility function; returns content of a URL
  5034. sub getpage {
  5035.     my ($this, $url, $dontdie) = @_;
  5036.  
  5037.     my $failed = 0;
  5038.     my $page = undef;
  5039.     # Try it first to retrieve the page with the "wget" shell command
  5040.     if (-x $sysdeps->{'wget'}) {
  5041.     if (open PAGE, "$sysdeps->{'wget'} $url -O - 2>/dev/null |") {
  5042.         $page = join('', <PAGE>);
  5043.         close PAGE;
  5044.     } else {
  5045.         $failed = 1;
  5046.     }
  5047.     # Then try to retrieve the page with the "curl" shell command
  5048.     } elsif (-x $sysdeps->{'curl'}) {
  5049.     if (open PAGE, "$sysdeps->{'curl'} $url -o - 2>/dev/null |") {
  5050.         $page = join('', <PAGE>);
  5051.         close PAGE;
  5052.     } else {
  5053.         $failed = 1;
  5054.     }
  5055.     } else {
  5056.     warn("WARNING: No tool for downloading web content found, please install either\n\"wget\" or \"curl\"! The result you got may be incorrect!\n");
  5057.     }
  5058.  
  5059.     if ((!$page) || ($failed)) {
  5060.     if ($dontdie) {
  5061.         return undef;
  5062.     } else {
  5063.         die ("http error: " . $url . "\n");
  5064.     }
  5065.     }
  5066.  
  5067.     return $page;
  5068. }
  5069.  
  5070. # Determine the margins as needed by "*ImageableArea"
  5071. sub getmarginsformarginrecord {
  5072.     my ($margins, $width, $height, $pagesize) = @_;
  5073.     if (!defined($margins)) {
  5074.     # No margins defined? Return invalid margins
  5075.     return (undef, undef, undef, undef);
  5076.     }
  5077.     # Defaults
  5078.     my $unit = 'pt';
  5079.     my $absolute = 0;
  5080.     my ($left, $right, $top, $bottom) = (undef, undef, undef, undef);
  5081.     # Check the general margins and then the particular paper size
  5082.     for my $i ('_general', $pagesize) {
  5083.     # Skip a section if it is not defined
  5084.     next if (!defined($margins->{$i}));
  5085.     # Determine the factor to calculate the margin in points (pt)
  5086.     $unit = (defined($margins->{$i}{'unit'}) ?
  5087.          $margins->{$i}{'unit'} : $unit);
  5088.     my $unitfactor = 1.0; # Default unit is points
  5089.     if ($unit =~ /^p/i) {
  5090.         $unitfactor = 1.0;
  5091.     } elsif ($unit =~ /^in/i) {
  5092.         $unitfactor = 72.0;
  5093.     } elsif ($unit =~ /^cm$/i) {
  5094.         $unitfactor = 72.0/2.54;
  5095.     } elsif ($unit =~ /^mm$/i) {
  5096.         $unitfactor = 72.0/25.4;
  5097.     } elsif ($unit =~ /^dots(\d+)dpi$/i) {
  5098.         $unitfactor = 72.0/$1;
  5099.     }
  5100.     # Convert the values to points
  5101.     ($left, $right, $top, $bottom) =
  5102.         ((defined($margins->{$i}{'left'}) ?
  5103.           $margins->{$i}{'left'} * $unitfactor : $left),
  5104.          (defined($margins->{$i}{'right'}) ?
  5105.           $margins->{$i}{'right'} * $unitfactor : $right),
  5106.          (defined($margins->{$i}{'top'}) ?
  5107.           $margins->{$i}{'top'} * $unitfactor : $top),
  5108.          (defined($margins->{$i}{'bottom'}) ?
  5109.           $margins->{$i}{'bottom'} * $unitfactor : $bottom));
  5110.     # Determine the absolute values
  5111.     $absolute = (defined($margins->{$i}{'absolute'}) ?
  5112.              $margins->{$i}{'absolute'} : $absolute);
  5113.     if (!$absolute){
  5114.         if (defined($margins->{$i}{'right'})) {
  5115.         $right = $width - $right;
  5116.         }
  5117.         if (defined($margins->{$i}{'top'})) {
  5118.         $top = $height - $top;
  5119.         }
  5120.     }
  5121.     }
  5122.     $left = sprintf("%.2f", $left) if $left =~ /\./;
  5123.     $right = sprintf("%.2f", $right) if $right =~ /\./;
  5124.     $top = sprintf("%.2f", $top) if $top =~ /\./;
  5125.     $bottom = sprintf("%.2f", $bottom) if $bottom =~ /\./;
  5126.     return ($left, $right, $top, $bottom);
  5127. }
  5128.  
  5129. sub getmargins {
  5130.     my ($dat, $width, $height, $pagesize) = @_;
  5131.     # Determine the unprintable margins
  5132.     my ($left, $right, $top, $bottom) = (undef, undef, undef, undef);
  5133.     # Margins from printer database entry
  5134.     my ($pleft, $pright, $ptop, $pbottom) =
  5135.     getmarginsformarginrecord($dat->{'printermargins'}, 
  5136.                   $width, $height, $pagesize);
  5137.     # Margins from driver database entry
  5138.     my ($dleft, $dright, $dtop, $dbottom) =
  5139.     getmarginsformarginrecord($dat->{'drivermargins'}, 
  5140.                   $width, $height, $pagesize);
  5141.     # Margins from printer/driver combo
  5142.     my ($cleft, $cright, $ctop, $cbottom) =
  5143.     getmarginsformarginrecord($dat->{'combomargins'}, 
  5144.                   $width, $height, $pagesize);
  5145.     # Left margin
  5146.     if (defined($pleft)) {$left = $pleft};
  5147.     if (defined($dleft) &&
  5148.     (!defined($left) || ($dleft > $left))) {$left = $dleft};
  5149.     if (defined($cleft) &&
  5150.     (!defined($left) || ($cleft > $left))) {$left = $cleft};
  5151.     # Right margin
  5152.     if (defined($pright)) {$right = $pright};
  5153.     if (defined($dright) &&
  5154.     (!defined($right) || ($dright < $right))) {$right = $dright};
  5155.     if (defined($cright) &&
  5156.     (!defined($right) || ($cright < $right))) {$right = $cright};
  5157.     # Top margin
  5158.     if (defined($ptop)) {$top = $ptop};
  5159.     if (defined($dtop) &&
  5160.     (!defined($top) || ($dtop < $top))) {$top = $dtop};
  5161.     if (defined($ctop) &&
  5162.     (!defined($top) || ($ctop < $top))) {$top = $ctop};
  5163.     # Bottom margin
  5164.     if (defined($pbottom)) {$bottom = $pbottom};
  5165.     if (defined($dbottom) &&
  5166.     (!defined($bottom) || ($dbottom > $bottom))) {$bottom = $dbottom};
  5167.     if (defined($cbottom) &&
  5168.     (!defined($bottom) || ($dbottom > $bottom))) {$bottom = $cbottom};
  5169.     # Safe margins when margin info is missing
  5170.     my $tborder = 36;
  5171.     my $bborder = 36;
  5172.     my $lborder = 18;
  5173.     my $rborder = 18;
  5174.     $left = $lborder if !defined($left);
  5175.     $right = $width - $rborder if !defined($right);
  5176.     $top = $height - $tborder if !defined($top);
  5177.     $bottom = $bborder if !defined($bottom);
  5178.     # If we entered with $width == 0 and $height == 0, we mean
  5179.     # relative margins, so correct the signs
  5180.     if ($width == 0) {$right = -$right};
  5181.     if ($height == 0) {$top = -$top};
  5182.     # Clean up output
  5183.     $left =~ s/^\s*-0\s*$/0/;
  5184.     $right =~ s/^\s*-0\s*$/0/;
  5185.     $top =~ s/^\s*-0\s*$/0/;
  5186.     $bottom =~ s/^\s*-0\s*$/0/;
  5187.     # Return the results
  5188.     return ($left, $right, $top, $bottom);
  5189. }
  5190.  
  5191. # Generate a translation/longname from a shortname
  5192. sub longname {
  5193.     my $shortname = $_[0];
  5194.     # A space before every upper-case letter in the middle preceeded by
  5195.     # a lower-case one
  5196.     $shortname =~ s/([a-z])([A-Z])/$1 $2/g;
  5197.     # If there are three or more upper-case letters, assume the last as
  5198.     # the beginning of the next word, the others as an abbreviation
  5199.     $shortname =~ s/([A-Z][A-Z]+)([A-Z][a-z])/$1 $2/g;
  5200.     return $shortname;
  5201. }
  5202.  
  5203. # Prepare strings for being part of an HTML document by, converting
  5204. # "<" to "<", ">" to ">", "&" to "&", "\"" to """,
  5205. # and "'" to  "'"
  5206. sub htmlify {
  5207.     my $str = $_[0];
  5208.     $str =~ s!&!&!g;
  5209.     $str =~ s/\</\</g;
  5210.     $str =~ s/\>/\>/g;
  5211.     $str =~ s/\"/\"/g;
  5212.     $str =~ s/\'/\'/g;
  5213.     return $str;
  5214. }
  5215.  
  5216. # This splits RIP directives (PostScript comments which are
  5217. # foomatic-rip uses to build the RIP command line) into multiple lines
  5218. # of a fixed length, to avoid lines longer than 255 characters. The
  5219. # PPD specification does not allow such long lines.
  5220. sub ripdirective {
  5221.     my ($header, $content) = ($_[0], htmlify($_[1]));
  5222.     # If possible, make lines of this length
  5223.     my $maxlength = 72;
  5224.     # Header of continuation line
  5225.     my $continueheader = "";
  5226.     # Two subsequent ampersands are not possible in an htmlified string,
  5227.     # so we can use them at the line end to mark that the current line
  5228.     # continues on the next line. A newline without this is also a newline
  5229.     # in the decoded string
  5230.     my $continuelineend = "&&";
  5231.     # output string
  5232.     my $out;
  5233.     # The colon and the quote after the header must be on the line with
  5234.     # the header
  5235.     $header .= ": \"";
  5236.     # How much of the current line is left?
  5237.     my $freelength = $maxlength - length($header) -
  5238.     length($continuelineend);
  5239.     # Add the header
  5240.     if ($freelength < 0) {
  5241.     # header longer than $maxlength, don't break it
  5242.     $out = "$header$continuelineend\n$continueheader";
  5243.     $freelength = $maxlength - length($continueheader) -
  5244.         length($continuelineend);
  5245.     } else {
  5246.     $out = "$header";
  5247.     }
  5248.     $content .= "\"";
  5249.     # Go through every line of the $content
  5250.     for my $l (split ("\n", $content)) {
  5251.     while ($l) {
  5252.         # Take off $maxlength portions until the string is used up
  5253.         if (length($l) < $freelength) {
  5254.         $freelength = length($l);
  5255.         }
  5256.         my $line = substr($l, 0, $freelength, "");
  5257.         # Add the portion 
  5258.         $out .= $line;
  5259.         # Finish the line
  5260.         $freelength = $maxlength - length($continueheader) -
  5261.         length($continuelineend);
  5262.         if ($l) {
  5263.         # Line continues in next line
  5264.         $out .= "$continuelineend\n$continueheader";
  5265.         } else {
  5266.         # line ends
  5267.         $out .= "\n";
  5268.         last;
  5269.         }
  5270.     }
  5271.     }
  5272.     # Remove trailing newline
  5273.     $out = substr($out, 0, -1);
  5274.     return $out;
  5275. }
  5276.  
  5277.  
  5278. # PPD boilerplate template
  5279.  
  5280. sub get_tmpl_paperdimension {
  5281.     return <<ENDPDTEMPL;
  5282. *% Generic PaperDimension; evidently there was no normal PageSize argument
  5283.  
  5284. *DefaultPaperDimension: Letter
  5285. *PaperDimension Letter:    "612 792"
  5286. *PaperDimension Legal:    "612 1008"
  5287. *PaperDimension A4:    "595 842"
  5288. ENDPDTEMPL
  5289. }
  5290.  
  5291. sub get_tmpl {
  5292.     return <<ENDTMPL;
  5293. *PPD-Adobe: "4.3"
  5294. \@\@POSTPIPE\@\@*%
  5295. \@\@HEADCOMMENT\@\@
  5296. *%
  5297. *% You may save this file as '\@\@SAVETHISAS\@\@'
  5298. *%
  5299. *%
  5300. *FormatVersion:    "4.3"
  5301. *FileVersion:    "1.1"
  5302. *LanguageVersion: English 
  5303. *LanguageEncoding: ISOLatin1
  5304. *PCFileName:    "\@\@PCFILENAME\@\@.PPD"
  5305. *Manufacturer:    "\@\@MANUFACTURER\@\@"
  5306. *Product:    "(\@\@PNPMODEL\@\@)"
  5307. *cupsVersion:    1.0
  5308. *cupsManualCopies: True
  5309. *cupsModelNumber:  2
  5310. *cupsFilter:    "application/vnd.cups-postscript 100 foomatic-rip"
  5311. *cupsFilter:    "application/vnd.cups-pdf 0 foomatic-rip"
  5312. *%pprRIP:        foomatic-rip other
  5313. *ModelName:     "\@\@MODEL\@\@"
  5314. *ShortNickName: "\@\@SHORTNICKNAME\@\@"
  5315. *NickName:      "\@\@NICKNAME\@\@"
  5316. *PSVersion:    "(3010.000) 550"
  5317. *PSVersion:    "(3010.000) 651"
  5318. *PSVersion:    "(3010.000) 652"
  5319. *PSVersion:    "(3010.000) 653"
  5320. *PSVersion:    "(3010.000) 704"
  5321. *PSVersion:    "(3010.000) 705"
  5322. *PSVersion:    "(3010.000) 800"
  5323. *PSVersion:    "(3010.000) 815"
  5324. *PSVersion:    "(3010.000) 850"
  5325. *PSVersion:    "(3010.000) 860"
  5326. *PSVersion:    "(3010.000) 861"
  5327. *PSVersion:    "(3010.000) 862"
  5328. *PSVersion:    "(3010.000) 863"
  5329. *PSVersion:    "(3010.000) 864"
  5330. *PSVersion:    "(3010.000) 870"
  5331. *LanguageLevel:    "3"
  5332. \@\@COLOR\@\@
  5333. *FileSystem:    False
  5334. *Throughput:    "1"
  5335. *LandscapeOrientation: Plus90
  5336. *TTRasterizer:    Type42
  5337. \@\@IEEE1284\@\@
  5338. \@\@DRIVERPROPERTIES\@\@
  5339. \@\@EXTRALINES\@\@
  5340. \@\@OTHERSTUFF\@\@
  5341.  
  5342. \@\@OPTIONS\@\@
  5343.  
  5344. *% Generic boilerplate PPD stuff as standard PostScript fonts and so on
  5345.  
  5346. *DefaultFont: Courier
  5347. *Font AvantGarde-Book: Standard "(001.006S)" Standard ROM
  5348. *Font AvantGarde-BookOblique: Standard "(001.006S)" Standard ROM
  5349. *Font AvantGarde-Demi: Standard "(001.007S)" Standard ROM
  5350. *Font AvantGarde-DemiOblique: Standard "(001.007S)" Standard ROM
  5351. *Font Bookman-Demi: Standard "(001.004S)" Standard ROM
  5352. *Font Bookman-DemiItalic: Standard "(001.004S)" Standard ROM
  5353. *Font Bookman-Light: Standard "(001.004S)" Standard ROM
  5354. *Font Bookman-LightItalic: Standard "(001.004S)" Standard ROM
  5355. *Font Courier: Standard "(002.004S)" Standard ROM
  5356. *Font Courier-Bold: Standard "(002.004S)" Standard ROM
  5357. *Font Courier-BoldOblique: Standard "(002.004S)" Standard ROM
  5358. *Font Courier-Oblique: Standard "(002.004S)" Standard ROM
  5359. *Font Helvetica: Standard "(001.006S)" Standard ROM
  5360. *Font Helvetica-Bold: Standard "(001.007S)" Standard ROM
  5361. *Font Helvetica-BoldOblique: Standard "(001.007S)" Standard ROM
  5362. *Font Helvetica-Narrow: Standard "(001.006S)" Standard ROM
  5363. *Font Helvetica-Narrow-Bold: Standard "(001.007S)" Standard ROM
  5364. *Font Helvetica-Narrow-BoldOblique: Standard "(001.007S)" Standard ROM
  5365. *Font Helvetica-Narrow-Oblique: Standard "(001.006S)" Standard ROM
  5366. *Font Helvetica-Oblique: Standard "(001.006S)" Standard ROM
  5367. *Font NewCenturySchlbk-Bold: Standard "(001.009S)" Standard ROM
  5368. *Font NewCenturySchlbk-BoldItalic: Standard "(001.007S)" Standard ROM
  5369. *Font NewCenturySchlbk-Italic: Standard "(001.006S)" Standard ROM
  5370. *Font NewCenturySchlbk-Roman: Standard "(001.007S)" Standard ROM
  5371. *Font Palatino-Bold: Standard "(001.005S)" Standard ROM
  5372. *Font Palatino-BoldItalic: Standard "(001.005S)" Standard ROM
  5373. *Font Palatino-Italic: Standard "(001.005S)" Standard ROM
  5374. *Font Palatino-Roman: Standard "(001.005S)" Standard ROM
  5375. *Font Symbol: Special "(001.007S)" Special ROM
  5376. *Font Times-Bold: Standard "(001.007S)" Standard ROM
  5377. *Font Times-BoldItalic: Standard "(001.009S)" Standard ROM
  5378. *Font Times-Italic: Standard "(001.007S)" Standard ROM
  5379. *Font Times-Roman: Standard "(001.007S)" Standard ROM
  5380. *Font ZapfChancery-MediumItalic: Standard "(001.007S)" Standard ROM
  5381. *Font ZapfDingbats: Special "(001.004S)" Standard ROM
  5382.  
  5383. ENDTMPL
  5384. }
  5385.  
  5386. # Determine the paper width and height in points from a given paper size
  5387. # name. Used for the "PaperDimension" and "ImageableArea" entries in PPD
  5388. # files.
  5389. #
  5390. # The paper sizes in the list are all sizes known to Ghostscript, all
  5391. # of Gutenprint, all sizes of HPIJS, and some others found in the data
  5392. # of printer drivers.
  5393.  
  5394. sub getpapersize {
  5395.     my $papersize = lc(join('', @_));
  5396.  
  5397.     my @sizetable = (
  5398.     ['germanlegalfanfold', '612 936'],
  5399.     ['halfletter',         '396 612'],
  5400.     ['letterwide',         '647 957'],
  5401.     ['lettersmall',        '612 792'],
  5402.     ['letter',             '612 792'],
  5403.     ['legal',              '612 1008'],
  5404.     ['postcard',           '283 416'],
  5405.     ['tabloid',            '792 1224'],
  5406.     ['ledger',             '1224 792'],
  5407.     ['tabloidextra',       '864 1296'],
  5408.     ['statement',          '396 612'],
  5409.     ['manual',             '396 612'],
  5410.     ['executive',          '522 756'],
  5411.     ['folio',              '612 936'],
  5412.     ['archa',              '648 864'],
  5413.     ['archb',              '864 1296'],
  5414.     ['archc',              '1296 1728'],
  5415.     ['archd',              '1728 2592'],
  5416.     ['arche',              '2592 3456'],
  5417.     ['usaarch',            '648 864'],
  5418.     ['usbarch',            '864 1296'],
  5419.     ['uscarch',            '1296 1728'],
  5420.     ['usdarch',            '1728 2592'],
  5421.     ['usearch',            '2592 3456'],
  5422.     ['a2.*invit.*',        '315 414'],
  5423.     ['b6-c4',              '354 918'],
  5424.     ['c7-6',               '229 459'],
  5425.     ['supera3-b',          '932 1369'],
  5426.     ['a3wide',             '936 1368'],
  5427.     ['a4wide',             '633 1008'],
  5428.     ['a4small',            '595 842'],
  5429.     ['sra4',               '637 907'],
  5430.     ['sra3',               '907 1275'],
  5431.     ['sra2',               '1275 1814'],
  5432.     ['sra1',               '1814 2551'],
  5433.     ['sra0',               '2551 3628'],
  5434.     ['ra4',                '609 864'],
  5435.     ['ra3',                '864 1218'],
  5436.     ['ra2',                '1218 1729'],
  5437.     ['ra1',                '1729 2437'],
  5438.     ['ra0',                '2437 3458'],
  5439.     ['a10',                '74 105'],
  5440.     ['a9',                 '105 148'],
  5441.     ['a8',                 '148 210'],
  5442.     ['a7',                 '210 297'],
  5443.     ['a6',                 '297 420'],
  5444.     ['a5',                 '420 595'],
  5445.     ['a4',                 '595 842'],
  5446.     ['a3',                 '842 1191'],
  5447.     ['a2',                 '1191 1684'],
  5448.     ['a1',                 '1684 2384'],
  5449.     ['a0',                 '2384 3370'],
  5450.     ['2a',                 '3370 4768'],
  5451.     ['4a',                 '4768 6749'],
  5452.     ['c10',                '79 113'],
  5453.     ['c9',                 '113 161'],
  5454.     ['c8',                 '161 229'],
  5455.     ['c7',                 '229 323'],
  5456.     ['c6',                 '323 459'],
  5457.     ['c5',                 '459 649'],
  5458.     ['c4',                 '649 918'],
  5459.     ['c3',                 '918 1298'],
  5460.     ['c2',                 '1298 1836'],
  5461.     ['c1',                 '1836 2599'],
  5462.     ['c0',                 '2599 3676'],
  5463.     ['b10.*jis',           '90 127'],
  5464.     ['b9.*jis',            '127 180'],
  5465.     ['b8.*jis',            '180 257'],
  5466.     ['b7.*jis',            '257 362'],
  5467.     ['b6.*jis',            '362 518'],
  5468.     ['b5.*jis',            '518 727'],
  5469.     ['b4.*jis',            '727 1029'],
  5470.     ['b3.*jis',            '1029 1459'],
  5471.     ['b2.*jis',            '1459 2063'],
  5472.     ['b1.*jis',            '2063 2919'],
  5473.     ['b0.*jis',            '2919 4127'],
  5474.     ['jis.*b10',           '90 127'],
  5475.     ['jis.*b9',            '127 180'],
  5476.     ['jis.*b8',            '180 257'],
  5477.     ['jis.*b7',            '257 362'],
  5478.     ['jis.*b6',            '362 518'],
  5479.     ['jis.*b5',            '518 727'],
  5480.     ['jis.*b4',            '727 1029'],
  5481.     ['jis.*b3',            '1029 1459'],
  5482.     ['jis.*b2',            '1459 2063'],
  5483.     ['jis.*b1',            '2063 2919'],
  5484.     ['jis.*b0',            '2919 4127'],
  5485.     ['b10.*iso',           '87 124'],
  5486.     ['b9.*iso',            '124 175'],
  5487.     ['b8.*iso',            '175 249'],
  5488.     ['b7.*iso',            '249 354'],
  5489.     ['b6.*iso',            '354 498'],
  5490.     ['b5.*iso',            '498 708'],
  5491.     ['b4.*iso',            '708 1000'],
  5492.     ['b3.*iso',            '1000 1417'],
  5493.     ['b2.*iso',            '1417 2004'],
  5494.     ['b1.*iso',            '2004 2834'],
  5495.     ['b0.*iso',            '2834 4008'],
  5496.     ['2b.*iso',            '4008 5669'],
  5497.     ['4b.*iso',            '5669 8016'],
  5498.     ['iso.*b10',           '87 124'],
  5499.     ['iso.*b9',            '124 175'],
  5500.     ['iso.*b8',            '175 249'],
  5501.     ['iso.*b7',            '249 354'],
  5502.     ['iso.*b6',            '354 498'],
  5503.     ['iso.*b5',            '498 708'],
  5504.     ['iso.*b4',            '708 1000'],
  5505.     ['iso.*b3',            '1000 1417'],
  5506.     ['iso.*b2',            '1417 2004'],
  5507.     ['iso.*b1',            '2004 2834'],
  5508.     ['iso.*b0',            '2834 4008'],
  5509.     ['iso.*2b',            '4008 5669'],
  5510.     ['iso.*4b',            '5669 8016'],
  5511.     ['b10envelope',        '87 124'],
  5512.     ['b9envelope',         '124 175'],
  5513.     ['b8envelope',         '175 249'],
  5514.     ['b7envelope',         '249 354'],
  5515.     ['b6envelope',         '354 498'],
  5516.     ['b5envelope',         '498 708'],
  5517.     ['b4envelope',         '708 1000'],
  5518.     ['b3envelope',         '1000 1417'],
  5519.     ['b2envelope',         '1417 2004'],
  5520.     ['b1envelope',         '2004 2834'],
  5521.     ['b0envelope',         '2834 4008'],
  5522.     ['b10',                '87 124'],
  5523.     ['b9',                 '124 175'],
  5524.     ['b8',                 '175 249'],
  5525.     ['b7',                 '249 354'],
  5526.     ['b6',                 '354 498'],
  5527.     ['b5',                 '498 708'],
  5528.     ['b4',                 '708 1000'],
  5529.     ['b3',                 '1000 1417'],
  5530.     ['b2',                 '1417 2004'],
  5531.     ['b1',                 '2004 2834'],
  5532.     ['b0',                 '2834 4008'],
  5533.     ['monarch',            '279 540'],
  5534.     ['dl',                 '311 623'],
  5535.     ['com10',              '297 684'],
  5536.     ['com.*10',            '297 684'],
  5537.     ['env10',              '297 684'],
  5538.     ['env.*10',            '297 684'],
  5539.     ['hagaki',             '283 420'],
  5540.     ['oufuku',             '420 567'],
  5541.     ['kaku',               '680 941'],
  5542.     ['long.*3',            '340 666'],
  5543.     ['long.*4',            '255 581'],
  5544.     ['foolscap',           '576 936'],
  5545.     ['flsa',               '612 936'],
  5546.     ['flse',               '648 936'],
  5547.     ['photo100x150',       '283 425'],
  5548.     ['photo200x300',       '567 850'],
  5549.     ['photofullbleed',     '298 440'],
  5550.     ['photo4x6',           '288 432'],
  5551.     ['photo',              '288 432'],
  5552.     ['wide',               '977 792'],
  5553.     ['card148',            '419 297'],
  5554.     ['envelope132x220',    '374 623'],
  5555.     ['envelope61/2',       '468 260'],
  5556.     ['supera',             '644 1008'],
  5557.     ['superb',             '936 1368'],
  5558.     ['fanfold5',           '612 792'],
  5559.     ['fanfold4',           '612 864'],
  5560.     ['fanfold3',           '684 792'],
  5561.     ['fanfold2',           '864 612'],
  5562.     ['fanfold1',           '1044 792'],
  5563.     ['fanfold',            '1071 792'],
  5564.     ['panoramic',          '595 1683'],
  5565.     ['plotter.*size.*a',   '612 792'],
  5566.     ['plotter.*size.*b',   '792 1124'],
  5567.     ['plotter.*size.*c',   '1124 1584'],
  5568.     ['plotter.*size.*d',   '1584 2448'],
  5569.     ['plotter.*size.*e',   '2448 3168'],
  5570.     ['plotter.*size.*f',   '3168 4896'],
  5571.     ['archlarge',          '162 540'],
  5572.     ['standardaddr',       '81 252'],
  5573.     ['largeaddr',          '101 252'],
  5574.     ['suspensionfile',     '36 144'],
  5575.     ['videospine',         '54 423'],
  5576.     ['badge',              '153 288'],
  5577.     ['archsmall',          '101 540'],
  5578.     ['videotop',           '130 223'],
  5579.     ['diskette',           '153 198'],
  5580.     ['76\.2mmroll',        '216 0'],
  5581.     ['69\.5mmroll',        '197 0'],
  5582.     ['roll',               '612 0'],
  5583.     ['custom',             '0 0']
  5584.     );
  5585.  
  5586.     # Remove prefixes which sometimes could appear
  5587.     $papersize =~ s/form_//;
  5588.  
  5589.     # Check whether the paper size name is in the list above
  5590.     for my $item (@sizetable) {
  5591.     if ($papersize =~ /@{$item}[0]/) {
  5592.         return @{$item}[1];
  5593.     }
  5594.     }
  5595.  
  5596.     # Check if we have a "<Width>x<Height>" format, assume the numbers are
  5597.     # given in inches
  5598.     if ($papersize =~ /(\d+)x(\d+)/) {
  5599.     my $w = $1 * 72;
  5600.     my $h = $2 * 72;
  5601.     return sprintf("%d %d", $w, $h);
  5602.     }
  5603.  
  5604.     # Check if we have a "w<Width>h<Height>" format, assume the numbers are
  5605.     # given in points
  5606.     if ($papersize =~ /w(\d+)h(\d+)/) {
  5607.     return "$1 $2";
  5608.     }
  5609.  
  5610.     # Check if we have a "w<Width>" format, assume roll paper with the given
  5611.     # width in points
  5612.     if ($papersize =~ /w(\d+)/) {
  5613.     return "$1 0";
  5614.     }
  5615.  
  5616.     # This paper size is absolutely unknown, issue a warning
  5617.     warn "WARNING: Unknown paper size: $papersize!";
  5618.     return "0 0";
  5619. }
  5620.  
  5621. # Get documentation for the printer/driver pair to print out. For
  5622. # "Execution Details" section of driver web pages on OpenPrinting
  5623.  
  5624. sub getexecdocs {
  5625.  
  5626.     my ($this) = $_[0];
  5627.  
  5628.     my $dat = $this->{'dat'};
  5629.  
  5630.     my @docs;
  5631.     
  5632.     # Construct the proper command line.
  5633.     my $commandline = htmlify($dat->{'cmd'});
  5634.  
  5635.     if ($commandline eq "") {return ();}
  5636.  
  5637.     my @letters = qw/A B C D E F G H I J K L M Z/;
  5638.     
  5639.     for my $spot (@letters) {
  5640.     
  5641.     if($commandline =~ m!\%$spot!) {
  5642.  
  5643.         my $arg;
  5644.       argument:
  5645.         for $arg (@{$dat->{'args'}}) {
  5646. #        for $arg (sort { $a->{'order'} <=> $b->{'order'} } 
  5647. #              @{$dat->{'args'}}) {
  5648.         
  5649.         # Only do arguments that go in this spot
  5650.         next argument if ($arg->{'spot'} ne $spot);
  5651.         # PJL arguments are not inserted at a spot in the command
  5652.         # line
  5653.         next argument if ($arg->{'style'} eq 'J');
  5654.         # Composite options are not interesting here
  5655.         next argument if ($arg->{'style'} eq 'X');
  5656.         
  5657.         my $name = htmlify($arg->{'name'});
  5658.         my $varname = htmlify($arg->{'varname'});
  5659.         my $cmd = htmlify($arg->{'proto'});
  5660.         my $comment = htmlify($arg->{'comment'});
  5661.         my $placeholder = "</TT><I><$name></I><TT>";
  5662.         my $default = htmlify($arg->{'default'});
  5663.         my $type = $arg->{'type'};
  5664.         my $cmdvar = "";
  5665.         my $gsarg1 = "";
  5666.         my $gsarg2 = "";
  5667.         if ($arg->{'style'} eq 'G') {
  5668.             $gsarg1 = ' -c "';
  5669.             $gsarg2 = '"';
  5670.             $cmd =~ s/\"/\\\"/g;
  5671.         }
  5672.         #my $leftbr = ($arg->{'required'} ? "" : "[");
  5673.         #my $rightbr = ($arg->{'required'} ? "" : "]");
  5674.         my $leftbr = "";
  5675.         my $rightbr = "";
  5676.     
  5677.         if ($type eq 'bool') {
  5678.             $cmdvar = "$leftbr$gsarg1$cmd$gsarg2$rightbr";
  5679.         } elsif ($type eq 'int' or $type eq 'float') {
  5680.             $cmdvar = sprintf("$leftbr$gsarg1$cmd$gsarg2$rightbr",$placeholder);
  5681.         } elsif ($type eq 'enum') {
  5682.             my $val;
  5683.             if ($val=valbyname($arg,$default)) {
  5684.             $cmdvar = sprintf("$leftbr$gsarg1$cmd$gsarg2$rightbr",
  5685.                       $placeholder);
  5686.             }
  5687.         }
  5688.         
  5689.         # Insert the processed argument in the commandline
  5690.         # just before every occurance of the spot marker.
  5691.         $cmdvar =~ s!^\[\ !\ \[!;
  5692.         $commandline =~ s!\%$spot!$cmdvar\%$spot!g;
  5693.         }
  5694.         
  5695.         # Remove the letter markers from the commandline
  5696.         $commandline =~ s!\%$spot!!g;
  5697.         
  5698.     }
  5699.     
  5700.     }
  5701.  
  5702.     $dat->{'excommandline'} = $commandline;
  5703.  
  5704.     push(@docs, "<B>Command Line</B><P>");
  5705.     push(@docs, "<BLOCKQUOTE><TT>$commandline</TT></BLOCKQUOTE><P>");
  5706.  
  5707.     my ($arg, @doctmp);
  5708.     my @pjlcommands = ();
  5709.   argt:
  5710.     for $arg (@{$dat->{'args'}}) {
  5711. #    for $arg (sort { $a->{'order'} <=> $b->{'order'} } 
  5712. #          @{$dat->{'args'}}) {
  5713.  
  5714.     # Composite options are not interesting here
  5715.     next argt if ($arg->{'style'} eq 'X');
  5716.  
  5717.     # Make sure that the longname/translation exists
  5718.     if (!$arg->{'comment'}) {
  5719.         $arg->{'comment'} = longname($arg->{'name'});
  5720.     }
  5721.  
  5722.     my $name = htmlify($arg->{'name'});
  5723.     my $cmd = htmlify($arg->{'proto'});
  5724.     my $comment = htmlify($arg->{'comment'});
  5725.     my $placeholder = "</TT><I><$name></I><TT>";
  5726.     if ($arg->{'style'} eq 'J') {
  5727.         $cmd = "\@PJL $cmd";
  5728.         my $sprintfcmd = $cmd;
  5729.         $sprintfcmd =~ s/\%(?!s)/\%\%/g;
  5730.         push (@pjlcommands, sprintf($sprintfcmd, $placeholder));
  5731.     }
  5732.  
  5733.     my $default = htmlify($arg->{'default'});
  5734.     my $type = $arg->{'type'};
  5735.     
  5736.     my $required = ($arg->{'required'} ? " required" : "n optional");
  5737.     my $pjl = ($arg->{'style'} eq 'J' ? "PJL " : "");
  5738.  
  5739.     if ($type eq 'bool') {
  5740.         my $name_false = htmlify($arg->{'name_false'});
  5741.         push(@doctmp,
  5742.          "<DL><DT><I>$name</I></DT>",
  5743.          "<DD>A$required boolean ${pjl}argument meaning $name if present or $name_false if not.<BR>",
  5744.          "$comment<BR>",
  5745.          "Prototype: <TT>$cmd</TT><BR>",
  5746.          "Default: ", $default ? "True" : "False",
  5747.          "</DD></DL><P>"
  5748.          );
  5749.  
  5750.     } elsif ($type eq 'int' or $type eq 'float') {
  5751.         my $max = (defined($arg->{'max'}) ? $arg->{'max'} : "none");
  5752.         my $min = (defined($arg->{'min'}) ? $arg->{'min'} : "none");
  5753.         my $sprintfcmd = $cmd;
  5754.         $sprintfcmd =~ s/\%(?!s)/\%\%/g;
  5755.         push(@doctmp,
  5756.          "<DL><DT><I>$name</I></DT>",
  5757.          "<DD>A$required $type ${pjl}argument.<BR>",
  5758.          "$comment<BR>",
  5759.          "Prototype: <TT>", sprintf($sprintfcmd, $placeholder),
  5760.          "</TT><BR>",
  5761.          "Default: <TT>$default</TT><BR>",
  5762.          "Range: <TT>$min <= $placeholder <= $max</TT>",
  5763.          "</DD></DL><P>"
  5764.          );
  5765.  
  5766.     } elsif ($type eq 'enum') {
  5767.         my ($val, $defstr);
  5768.         my (@choicelist) = ();
  5769.  
  5770.         for $val (@{$arg->{'vals'}}) {
  5771.  
  5772.         # Make sure that the longname/translation exists
  5773.         if (!$val->{'comment'}) {
  5774.             $val->{'comment'} = longname($val->{'value'});
  5775.         }
  5776.  
  5777.         my ($value, $comment, $driverval) = 
  5778.             (htmlify($val->{'value'}),
  5779.              htmlify($val->{'comment'}),
  5780.              htmlify($val->{'driverval'}));
  5781.  
  5782.         if (defined($driverval)) {
  5783.             if ($driverval eq "") {
  5784.             push(@choicelist,
  5785.                  "<LI>$value: $comment (<TT>$placeholder</TT> is left blank)</LI>");
  5786.             } else {
  5787.             my $widthheight = "";
  5788.             if (($name eq "PageSize") && ($value eq "Custom")) {
  5789.                 my $width = "</TT><I><Width></I><TT>";
  5790.                 my $height = "</TT><I><Height></I><TT>";
  5791.                 $driverval =~ s/\%0/$width/ or
  5792.                             $driverval =~ s/(\W)0(\W)/$1$width$2/ or
  5793.                             $driverval =~ s/^0(\W)/$width$1/m or
  5794.                             $driverval =~ s/(\W)0$/$1$width/m or
  5795.                             $driverval =~ s/^0$/$width/m;
  5796.                             $driverval =~ s/\%1/$height/ or
  5797.                             $driverval =~ s/(\W)0(\W)/$1$height$2/ or
  5798.                             $driverval =~ s/^0(\W)/$height$1/m or
  5799.                             $driverval =~ s/(\W)0$/$1$height/m or
  5800.                             $driverval =~ s/^0$/$height/m;
  5801.                 $widthheight = ", <I><Width></I> and <I><Height></I> are the page dimensions in points, 1/72 inches";
  5802.             }
  5803.             push(@choicelist,
  5804.                  "<LI>$value: $comment (<TT>$placeholder</TT> is '<TT>$driverval</TT>'$widthheight)</LI>");
  5805.             }
  5806.         } else {
  5807.             push(@choicelist,
  5808.              "<LI>$value: $comment (<TT>$placeholder</TT> is '<TT>$value</TT>')</LI>");
  5809.         }
  5810.         }
  5811.  
  5812.         my $sprintfcmd = $cmd;
  5813.         $sprintfcmd =~ s/\%(?!s)/\%\%/g;
  5814.         push(@doctmp,
  5815.          "<DL><DT><I>$name</I></DT>",
  5816.          "<DD>A$required enumerated choice ${pjl}argument.<BR>",
  5817.          "$comment<BR>",
  5818.          "Prototype: <TT>", sprintf($sprintfcmd, $placeholder),
  5819.          "</TT><BR>",
  5820.          "Default: $default",
  5821.          "<UL>", 
  5822.          join("", @choicelist), 
  5823.          "</UL></DD></DL><P>"
  5824.          );
  5825.  
  5826.     }
  5827.     }
  5828.  
  5829.     # Instructions for PJL commands
  5830.     if (($#pjlcommands > -1) && (defined($dat->{'pjl'}))) {
  5831.     #if (($#pjlcommands > -1)) {
  5832.     my @pjltmp;
  5833.     push(@pjltmp,
  5834.          "PJL arguments are not put into the command line, they must be put into a PJL header which is prepended to the actual job data which is generated by the command line shown above and sent to the printer. After the job data one can reset the printer via PJL. So a complete job looks as follows:<BLOCKQUOTE>",
  5835.          "<I><ESC></I>",
  5836.          # The "JOB" PJL command is not supported by all printers
  5837.          "<TT>%-12345X\@PJL</TT><BR>");
  5838.          #"<TT>%-12345X\@PJL JOB NAME=\"</TT>",
  5839.          #"<I><A job name></I>",
  5840.          #"<TT>\"</TT><BR>");
  5841.     for my $command (@pjlcommands) {
  5842.         push(@pjltmp,
  5843.          "<TT>$command</TT><BR>");
  5844.     }
  5845.     push(@pjltmp,
  5846.          "<I><The job data></I><BR>",
  5847.          "<I><ESC></I>",
  5848.          # The "JOB" PJL command is not supported by all printers
  5849.          "<TT>%-12345X\@PJL RESET</TT></BLOCKQUOTE><P>",
  5850.          #"<TT>%-12345X\@PJL EOJ</TT></BLOCKQUOTE><P>",
  5851.          "<I><ESC></I>",
  5852.          ": This is the ",
  5853.          "<I>ESC</I>",
  5854.          " character, ASCII code 27.<P>",
  5855.          #"<I><A job name></I>",
  5856.          #": The job name can be chosen arbitrarily, some printers show it on their front panel displays.<P>",
  5857.          "It is not required to give the PJL arguments, you can leave out some of them or you can even send only the job data without PJL header and PJL end-of-job mark.<P>");
  5858.     push(@docs, "<B>PJL</B><P>");
  5859.     push(@docs, @pjltmp);
  5860.     } elsif ((defined($dat->{'drivernopjl'})) && 
  5861.          ($dat->{'drivernopjl'} == 1) && 
  5862.          (defined($dat->{'pjl'}))) {
  5863.     my @pjltmp;
  5864.     push(@pjltmp,
  5865.          "This driver produces a PJL header with PJL commands internally and it is incompatible with extra PJL options merged into that header. Therefore there are no PJL options available when using this driver.<P>");
  5866.     push(@docs, "<B>PJL</B><P>");
  5867.     push(@docs, @pjltmp);
  5868.     }
  5869.  
  5870.     push(@docs, "<B>Options</B><P>");
  5871.  
  5872.     push(@docs, @doctmp);
  5873.  
  5874.     return @docs;
  5875.    
  5876. }
  5877.  
  5878. # Get a shorter summary documentation thing.
  5879. sub get_summarydocs {
  5880.     my ($this) = $_[0];
  5881.  
  5882.     my $dat = $this->{'dat'};
  5883.  
  5884.     my @docs;
  5885.  
  5886.     for my $arg (@{$dat->{'args'}}) {
  5887.  
  5888.     # Make sure that the longname/translation exists
  5889.     if (!$arg->{'comment'}) {
  5890.         $arg->{'comment'} = longname($arg->{'name'});
  5891.     }
  5892.  
  5893.     my ($name,
  5894.         $required,
  5895.         $type,
  5896.         $comment,
  5897.         $spot,
  5898.         $default) = ($arg->{'name'},
  5899.              $arg->{'required'},
  5900.              $arg->{'type'},
  5901.              $arg->{'comment'},
  5902.              $arg->{'spot'},
  5903.              $arg->{'default'});
  5904.     
  5905.     my $reqstr = ($required ? " required" : "n optional");
  5906.     push(@docs,
  5907.          "Option `$name':\n  A$reqstr $type argument.\n  $comment\n");
  5908.  
  5909.     push(@docs,
  5910.          "  This option corresponds to a PJL command.\n") 
  5911.         if ($spot eq 'Y');
  5912.     
  5913.     if ($type eq 'bool') {
  5914.         if (defined($default)) {
  5915.         my $defstr = ($default ? "True" : "False");
  5916.         push(@docs, "  Default: $defstr\n");
  5917.         }
  5918.         push(@docs, "  Example (true): `$name'\n");
  5919.         push(@docs, "  Example (false): `no$name'\n");
  5920.     } elsif ($type eq 'enum') {
  5921.         push(@docs, "  Possible choices:\n");
  5922.         my $exarg;
  5923.         for (@{$arg->{'vals'}}) {
  5924.  
  5925.         # Make sure that the longname/translation exists
  5926.         if (!$_->{'comment'}) {
  5927.             $_->{'comment'} = longname($_->{'value'});
  5928.         }
  5929.  
  5930.         my ($choice, $comment) = ($_->{'value'}, $_->{'comment'});
  5931.         push(@docs, "   * $choice: $comment\n");
  5932.         $exarg=$choice;
  5933.         }
  5934.         if (defined($default)) {
  5935.         push(@docs, "  Default: $default\n");
  5936.         }
  5937.         push(@docs, "  Example: `$name=$exarg'\n");
  5938.     } elsif ($type eq 'int' or $type eq 'float') {
  5939.         my ($max, $min) = ($arg->{'max'}, $arg->{'min'});
  5940.         my $exarg;
  5941.         if (defined($max)) {
  5942.         push(@docs, "  Range: $min <= x <= $max\n");
  5943.         $exarg=$max;
  5944.         }
  5945.         if (defined($default)) {
  5946.         push(@docs, "  Default: $default\n");
  5947.         $exarg=$default;
  5948.         }
  5949.         if (!$exarg) { $exarg=0; }
  5950.         push(@docs, "  Example: `$name=$exarg'\n");
  5951.     }
  5952.  
  5953.     push(@docs, "\n");
  5954.     }
  5955.  
  5956.     return @docs;
  5957.  
  5958. }
  5959.  
  5960. # About as obsolete as the other docs functions.  Why on earth are
  5961. # there three, anyway?!
  5962. sub getdocs {
  5963.     my ($this) = $_[0];
  5964.  
  5965.     my $dat = $this->{'dat'};
  5966.  
  5967.     my @docs;
  5968.  
  5969.     for my $arg (@{$dat->{'args'}}) {
  5970.  
  5971.     # Make sure that the longname/translation exists
  5972.     if (!$arg->{'comment'}) {
  5973.         $arg->{'comment'} = longname($arg->{'name'});
  5974.     }
  5975.  
  5976.     my ($name,
  5977.         $required,
  5978.         $type,
  5979.         $comment,
  5980.         $spot,
  5981.         $default) = ($arg->{'name'},
  5982.              $arg->{'required'},
  5983.              $arg->{'type'},
  5984.              $arg->{'comment'},
  5985.              $arg->{'spot'},
  5986.              $arg->{'default'});
  5987.     
  5988.     my $reqstr = ($required ? " required" : "n optional");
  5989.     push(@docs,
  5990.          "Option `$name':\n  A$reqstr $type argument.\n  $comment\n");
  5991.  
  5992.     push(@docs,
  5993.          "  This option corresponds to a PJL command.\n") 
  5994.         if ($spot eq 'Y');
  5995.     
  5996.     if ($type eq 'bool') {
  5997.         if (defined($default)) {
  5998.         my $defstr = ($default ? "True" : "False");
  5999.         push(@docs, "  Default: $defstr\n");
  6000.         }
  6001.         push(@docs, "  Example (true): `$name'\n");
  6002.         push(@docs, "  Example (false): `no$name'\n");
  6003.     } elsif ($type eq 'enum') {
  6004.         push(@docs, "  Possible choices:\n");
  6005.         my $exarg;
  6006.         for (@{$arg->{'vals'}}) {
  6007.  
  6008.         # Make sure that the longname/translation exists
  6009.         if (!$_->{'comment'}) {
  6010.             $_->{'comment'} = longname($_->{'value'});
  6011.         }
  6012.  
  6013.         my ($choice, $comment) = ($_->{'value'}, $_->{'comment'});
  6014.         push(@docs, "   * $choice: $comment\n");
  6015.         $exarg=$choice;
  6016.         }
  6017.         if (defined($default)) {
  6018.         push(@docs, "  Default: $default\n");
  6019.         }
  6020.         push(@docs, "  Example: `$name=$exarg'\n");
  6021.     } elsif ($type eq 'int' or $type eq 'float') {
  6022.         my ($max, $min) = ($arg->{'max'}, $arg->{'min'});
  6023.         my $exarg;
  6024.         if (defined($max)) {
  6025.         push(@docs, "  Range: $min <= x <= $max\n");
  6026.         $exarg=$max;
  6027.         }
  6028.         if (defined($default)) {
  6029.         push(@docs, "  Default: $default\n");
  6030.         $exarg=$default;
  6031.         }
  6032.         if (!$exarg) { $exarg=0; }
  6033.         push(@docs, "  Example: `$name=$exarg'\n");
  6034.     }
  6035.  
  6036.     push(@docs, "\n");
  6037.     }
  6038.  
  6039.     return @docs;
  6040.  
  6041. }
  6042.  
  6043. # Find a choice value hash by name.
  6044. # Operates on old dat structure...
  6045. sub valbyname {
  6046.     my ($arg,$name) = @_;
  6047.  
  6048.     my $val;
  6049.     for my $val (@{$arg->{'vals'}}) {
  6050.     return $val if (lc($name) eq lc($val->{'value'}));
  6051.     }
  6052.  
  6053.     return undef;
  6054. }
  6055.  
  6056. # replace numbers with fixed 6-digit number, set to lower case, replace
  6057. # non-alphanumeric characters by single spaces for ease of sorting
  6058. # ie: sort { normalizename($a) cmp normalizename($b) } @foo;
  6059. sub normalizename {
  6060.     my $n = $_[0];
  6061.  
  6062.     $n =~ s/[\d\.]+/sprintf("%013.6f", $&)/eg;
  6063.     $n = normalize($n);
  6064.     return $n;
  6065. }
  6066.  
  6067.  
  6068. # Load an XML object from the library
  6069. # You specify the relative file path (to .../db/), less the .xml on the end.
  6070. sub _get_object_xml {
  6071.     my ($this, $file, $quiet) = @_;
  6072.  
  6073.     open XML, "$libdir/db/$file.xml"
  6074.     or do { warn "Cannot open file $libdir/db/$file.xml\n"
  6075.             if !$quiet;
  6076.         return undef; };
  6077.     my $xml = join('', (<XML>));
  6078.     close XML;
  6079.  
  6080.     return $xml;
  6081. }
  6082.  
  6083. # Write an XML object from the library
  6084. # You specify the relative file path (to .../db/), less the .xml on the end.
  6085. sub _set_object_xml {
  6086.     my ($this, $file, $stuff, $cache) = @_;
  6087.  
  6088.     my $dir = "$libdir/db";
  6089.     my $xfile = "$dir/$file.xml";
  6090.     umask 0002;
  6091.     open XML, ">$xfile.$$"
  6092.     or do { warn "Cannot write file $xfile.$$\n";
  6093.         return undef; };
  6094.     print XML $stuff;
  6095.     close XML;
  6096.     rename "$xfile.$$", $xfile
  6097.     or die "Cannot rename $xfile.$$ to $xfile\n";
  6098.  
  6099.     return 1;
  6100. }
  6101.  
  6102. # Get a list of XML filenames from a library directory.  These could then be
  6103. # read with _get_object_xml.
  6104. sub _get_xml_filelist {
  6105.     my ($this, $dir) = @_;
  6106.  
  6107.     if (!defined($this->{"names-$dir"})) {
  6108.     opendir DRV, "$libdir/db/$dir"
  6109.         or die 'Cannot find source db for $dir\n';
  6110.     my $driverfile;
  6111.     while($driverfile = readdir(DRV)) {
  6112.         next if ($driverfile !~ m!^(.+)\.xml$!);
  6113.         push(@{$this->{"names-$dir"}}, $1);
  6114.     }
  6115.     closedir(DRV);
  6116.     }
  6117.  
  6118.     return @{$this->{"names-$dir"}};
  6119. }
  6120.  
  6121.  
  6122. # Return a Perl structure in eval-able ascii format
  6123. sub getascii {
  6124.     my ($this) = $_[0];
  6125.     if (! $this->{'dat'}) {
  6126.     $this->getdat();
  6127.     }
  6128.     
  6129.     local $Data::Dumper::Purity=1;
  6130.     local $Data::Dumper::Indent=1;
  6131.  
  6132.     # Encase data for inclusion in PPD file
  6133.     return Dumper($this->{'dat'});
  6134. }
  6135.  
  6136. # Return list of printer makes
  6137. sub get_makes {
  6138.     my ($this) = @_;
  6139.  
  6140.     my @makes;
  6141.     my %seenmakes;
  6142.     my $p;
  6143.     for $p (@{$this->get_overview()}) {
  6144.     my $make = $p->{'make'};
  6145.     push (@makes, $make) 
  6146.         if ! $seenmakes{$make}++;
  6147.     }
  6148.     
  6149.     return @makes;
  6150.     
  6151. }
  6152.  
  6153. # get a list of model names from a make
  6154. sub get_models_by_make {
  6155.     my ($this, $wantmake) = @_;
  6156.  
  6157.     my $over = $this->get_overview();
  6158.  
  6159.     my @models;
  6160.     my $p;
  6161.     for $p (@{$over}) {
  6162.     push (@models, $p->{'model'}) 
  6163.         if ($wantmake eq $p->{'make'});
  6164.     }
  6165.  
  6166.     return @models;
  6167. }
  6168.  
  6169. # get a printer id from a make/model
  6170. sub get_printer_from_make_model {
  6171.     my ($this, $wantmake, $wantmodel) = @_;
  6172.  
  6173.     my $over = $this->get_overview();
  6174.     my $p;
  6175.     for $p (@{$over}) {
  6176.     return $p->{'id'} if ($p->{'make'} eq $wantmake
  6177.                   and $p->{'model'} eq $wantmodel);
  6178.     }
  6179.  
  6180.     return undef;
  6181. }
  6182.  
  6183. sub get_javascript2 {
  6184.  
  6185.     my ($this, $models, $oids) = @_;
  6186.  
  6187.     my @swit;
  6188.     my $mak;
  6189.     my $else = "";
  6190.     my @makes;
  6191.     my %modelhash;
  6192.     my %oidhash;
  6193.     if ($models) {
  6194.     %modelhash = %{$models};
  6195.     @makes = sort {normalizename($a) cmp normalizename($b) } (keys %modelhash);
  6196.     } else {
  6197.     @makes = sort {normalizename($a) cmp normalizename($b) } ($this->get_makes());
  6198.     }
  6199.     if ($oids) {
  6200.     %oidhash = %{$oids};
  6201.     }
  6202.     for $mak (@makes) {
  6203.     push (@swit,
  6204.           " $else if (make == \"$mak\") {\n");
  6205.  
  6206.     my $ct = 0;
  6207.  
  6208.     my @makemodels;
  6209.     if ($models) {
  6210.         @makemodels = @{$modelhash{$mak}};
  6211.     } else {
  6212.         @makemodels = ($this->get_models_by_make($mak));
  6213.     }
  6214.     my $mod;
  6215.     for $mod (sort {normalizename($a) cmp normalizename($b) } 
  6216.           @makemodels) {
  6217.         
  6218.         my $p;
  6219.         $p = $this->get_printer_from_make_model($mak, $mod);
  6220.         if (defined($p)) {
  6221.         push (@swit,
  6222.               "      o[i++]=new Option(\"$mod\", \"$p\");\n");
  6223.         $ct++;
  6224.         } else {
  6225.         my $oid;
  6226.         if ($oids) {
  6227.             $oid = $oidhash{$mak}{$mod};
  6228.         } else {
  6229.             $oid = "$mak-$mod";
  6230.             $oid =~ s/ /_/g;
  6231.             $oid =~ s/\+/plus/g;
  6232.             $oid =~ s/[^A-Za-z0-9_\-]//g;
  6233.             $oid =~ s/__+/_/g;
  6234.             $oid =~ s/_$//;
  6235.         }
  6236.         push (@swit,
  6237.               "      o[i++]=new Option(\"$mod\", \"$oid\");\n");
  6238.         $ct++;
  6239.         }
  6240.     }
  6241.  
  6242.     if (!$ct) {
  6243.         push(@swit,
  6244.          "      o[i++]=new Option(\"No Printers\", \"0\");\n");
  6245.     }
  6246.  
  6247.     push (@swit,
  6248.           "    }");
  6249.     $else = "else";
  6250.     }
  6251.  
  6252.     my $switch = join('',@swit);
  6253.  
  6254.     my $javascript = '
  6255.        function reflectMake(makeselector, modelselector) {
  6256.      //
  6257.      // This function is called when makeselector changes
  6258.      // by an onchange thingy on the makeselector.
  6259.      //
  6260.  
  6261.      // Get the value of the OPTION that just changed
  6262.      selected_value=makeselector.options[makeselector.selectedIndex].value;
  6263.      // Get the text of the OPTION that just changed
  6264.      make=makeselector.options[makeselector.selectedIndex].text;
  6265.  
  6266.      o = new Array;
  6267.      i=0;
  6268.  
  6269.      ' . $switch . '    if (i==0) {
  6270.        alert("Error: that dropdown should do something, but it doesnt");
  6271.      } else {
  6272.        modelselector.length=o.length;
  6273.        for (i=0; i < o.length; i++) {
  6274.          modelselector.options[i]=o[i];
  6275.        }
  6276.        modelselector.options[0].selected=true;
  6277.      }
  6278.  
  6279.        }
  6280.      ';
  6281.  
  6282.     return $javascript;
  6283. }
  6284.  
  6285.  
  6286.  
  6287.  
  6288. # Modify comments text to contain only what it should:
  6289. #
  6290. # <a>, <p>, <br> (<br> -> <p>)
  6291. #
  6292. sub comment_filter {
  6293.     my ($text) = @_;
  6294.  
  6295.     my $fake = ("INSERTFIXEDTHINGHERE" . sprintf("%06x", rand(1000000)));
  6296.     my %replacements;
  6297.     my $num = 1;
  6298.  
  6299.     # extract all the A href tags
  6300.     my $replace = "ANCHOR$fake$num";
  6301.     while ($text =~ 
  6302.        s!(<\s*a\s+href\s*=\s*['"]([^'"]+)['"]\s*>)!$replace!i) {
  6303.     $replacements{$replace} = $1;
  6304.     $num++;
  6305.     $replace = "ANCHOR$fake$num";
  6306.     }
  6307.  
  6308.     # extract all the A tail tags
  6309.     $replace = "ANCHORTAIL$fake$num";
  6310.     while ($text =~ 
  6311.        s!(<\s*/\s*a\s*>)!$replace!i) {
  6312.     $replacements{$replace} = $1;
  6313.     $num++;
  6314.     $replace = "ANCHOR$fake$num";
  6315.     }
  6316.  
  6317.     # extract all the P tags
  6318.     $replace = "PARA$fake$num";
  6319.     while ($text =~ 
  6320.        s!(<\s*p\s*>)!$replace!i) {
  6321.  
  6322.     $replacements{$replace} = $1;
  6323.     $num++;
  6324.     $replace = "PARA$fake$num";
  6325.     }
  6326.  
  6327.     # extract all the BR tags
  6328.     $replace = "PARA$fake$num";
  6329.     while ($text =~ 
  6330.        s!(<\s*br\s*>)!$replace!i) {
  6331.  
  6332.     $replacements{$replace} = $1;
  6333.     $num++;
  6334.     $replace = "PARA$fake$num";
  6335.     }
  6336.  
  6337.     # Now it's just clean text; remove all tags and &foo;s
  6338.     $text =~ s!<[^>]+>! !g;
  6339.     $text =~ s!&!&!g;
  6340.     $text =~ s!<!<!g;
  6341.     $text =~ s!>!>!g;
  6342.     $text =~ s!&[^;]+?;! !g;
  6343.  
  6344.     # Now rewrite into our teeny-html subset
  6345.     $text =~ s!&!&!g;
  6346.     $text =~ s!<!<!g;
  6347.     $text =~ s!>!>!g;
  6348.  
  6349.     # And reinsert the few things we wanted to preserve
  6350.     for (keys(%replacements)) {
  6351.     my ($k, $r) = ($_, $replacements{$_});
  6352.     $text =~ s!$k!$r!;
  6353.     }
  6354.  
  6355. #    print STDERR "$text";
  6356.  
  6357.     return $text;
  6358. }
  6359.  
  6360. 1;
  6361.